Moderne Programmierung mit Swift, Teil 2

Swift stellt einige Regeln zur Initialisierung und Vererbung auf. Auch oder gerade weil einiges davon im Hintergrund geschieht, ist es wichtig, über Besonderheiten Bescheid zu wissen.

Sprachen  –  31 Kommentare
Moderne Programmierung mit Swift, Teil 2

Mit Initialisierung und Vererbung hat man in der professionellen Entwicklung ständig zu tun. Der zweite Teil der Artikelreihe "Moderne Programmierung mit Swift" (siehe Teil 1) setzt sich intensiv mit beidem auseinander. Er zeigt die Eigenheiten bei der Initialisierung und gibt Aufschluss darüber, was in dem Kontext insbesondere bei der Vererbung von Reference Types zu beachten ist. Abschließend gibt der Beitrag einen Überblick über den Status Quo in Sachen Type Checking und Type Casting.

Von allen Typen, die in Swift existieren oder die Entwickler selbst definieren – ganz gleich ob auf Grundlage einer Enumeration, Structure oder Klasse –, lassen sich Instanzen erstellen. Dazu kommen die sogenannten Initializer samt entsprechender Syntax zum Einsatz.

Sie sind mit einer Type-Methode vergleichbar, allerdings lassen sich Initializer ausschließlich zum Erzeugen neuer Instanzen des zugrundeliegenden Typs nutzen. Um einen Initializer in Swift zu deklarieren, ist das Schlüsselwort init zu verwenden, gefolgt von optionalen Parametern. Wie eine Implementierung von Initializern in Swift aussehen kann, zeigt der unten stehende Quellcode. Darin wird eine Structure Person mit drei Properties deklariert: firstName, lastName und dem Optional age. Daneben bringt die Structure insgesamt drei Initializer mit, die jeweils unterschiedliche Parameter entgegennehmen und entsprechend den Properties des Typs Person passende Werte zuweisen.

struct Person {
let firstName: String
let lastName: String
var age: UInt?

init() {
firstName = "Max"
lastName = "Mustermann"
}

init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}

init(firstName: String, lastName: String, age: UInt?) {
self.firstName = firstName
self.lastName = lastName
self.age = age
}
}

Hierbei fallen mehrere Besonderheiten auf. Zunächst geben Initializer in Swift nicht explizit einen Wert zurück. Zwar hat ihr Einsatz immer zur Folge, dass das Programm eine neue Instanz des zugrundeliegenden Typs erstellt, doch anders als bei manch anderer Programmiersprache (wie Objective-C) ist die eigentliche Rückgabe der erzeugten Instanz nicht explizit anzugeben.

Des Weiteren gilt bei Initializern in Swift ein elementarer Grundsatz: Alle nicht-optionalen Properties eines Typs muss am Ende der Initialisierung einen Wert aufweisen. Im gezeigten Beispiel sind die Properties firstName und lastName nicht optional und verfügen ebensowenig über einen Standardwert, weshalb jeder Initializer diesen Properties zwingend einen Wert zuweisen muss.

Initializer besitzen in Swift keine eigenen Bezeichner, über die sie sich ansprechen lassen; sie werden allesamt lediglich mit init deklariert und unterscheiden sich durch ihre Parameter voneinander. Es ist somit nicht erlaubt, zwei Initializer innerhalb eines Typs mit denselben Argument Labels zu deklarieren.

Um nun einen der verfügbaren Initializer aufzurufen und so eine neue Instanz eines Typs zu erzeugen, wird zunächst der Name des gewünschten Typs angegeben, gefolgt von einem runden Klammernpaar (als würde man eine Type-Methode aufrufen). Darin sind dann die Parameter des gewünschten Initializer anzugeben. Das folgende Listing zeigt beispielhaft die Aufrufe der drei Initializer des Typs Person.

let firstPerson = Person()
let secondPerson = Person(firstName: "Thomas", lastName: "Sillmann")
let thirdPerson = Person(firstName: "Thomas", lastName: "Sillmann", age: 28)

Sind für einen Typ keine expliziten Initializer mittels init deklariert, lässt sich der Default Initializer verwenden, um eine Instanz zu erzeugen. Er besitzt keinerlei Parameter – vergleichbar mit dem Initializer, mit dem im vorigen Beispiel die Instanz für die Konstante firstPerson erzeugt wurde. Damit der Default Initializer zur Verfügung steht, müssen zwei Bedingungen erfüllt sein:

  1. Innerhalb des entsprechenden Typs darf kein einziger Initializer deklariert werden (weder mit noch ohne Parameter), da er sonst den Default Initializer ersetzen würde.
  2. Jede Property des Typs muss entweder mit einem Standardwert belegt oder als Optional deklariert sein.

Außer dem Default Initializer gibt es einen weiteren Standard-Initializer in Swift, der exklusiv bei Strukturen zum Einsatz kommt: der sogenannte Memberwise Initializer. Damit er zur Verfügung steht, darf – genau wie beim Default Initializer – kein Initializer innerhalb der zugrundeliegenden Structure deklariert sein. Ist das der Fall, erzeugt Swift automatisch einen passenden Initializer für die Structure, für den für jede Property des Typs ein passender Parameter deklariert wird. Somit eignet sich der Memberwise Initializer dazu, automatisch für alle Properties einer Structure bei der Initialisierung einen passenden Wert zu setzen.

Dabei steht der Memberwise Initializer auch zur Verfügung, wenn eine oder mehrere Properties einer Structure weder einen Standardwert besitzen noch als Optional deklariert sind. Bei Klassen oder Enumerations ist in dem Fall zwingend ein passender Initializer zu ergänzen, der den Properties während der Initialisierung einen Wert zuweist.

Das folgende Listing zeigt eine deutlich abgespeckte Deklaration der zuvor erstellten Structure Person; sie verfügt nun lediglich über ihre drei Properties und keinen expliziten Initializer mehr. Da es sich aber um eine Structure handelt, steht automatisch der genannte Memberwise Initializer zur Verfügung, mit dem sich Instanzen vom Typ Person erzeugen lassen. Das Erstellen einer solchen Instanz ist beispielhaft nach der überarbeiteten Deklaration des Typs aufgeführt.

struct Person {

let firstName: String
let lastName: String
var age: UInt?

}

let aPerson = Person(firstName: "Thomas", lastName: "Sillmann", age: 28)

Wie geschrieben ist dieser automatisch erzeugte Memberwise Initializer ausschließlich Strukturen vorbehalten. Bei Aufzählungen und Klassen hätte die gezeigte und gekürzte Deklaration des Typs Person zu einem Fehler geführt, da kein expliziter Initializer zur Verfügung steht und die Properties firstName und lastName weder einen Standardwert besitzen noch optional sind.

Um Code-Dopplungen zu vermeiden, können Initializer in Swift auch andere Initializer desselben Typs aufrufen (wobei es dabei bei Reference Types in Sachen Vererbung einige Dinge zu beachten gibt, dazu später im Abschnitt "Zwei-Phasen-Initialisierung" mehr). Das zu Beginn des Artikels gezeigte Beispiel zur Deklaration des Typs Person lässt sich wie im Folgenden gezeigt abkürzen. Dort rufen die ersten beiden Initializer jeweils einen anderen Initializer auf und reichen ihre Informationen weiter.

struct Person {

let firstName: String
let lastName: String

var age: UInt?
init() {
self.init(firstName: "Max", lastName: "Mustermann")
}

init(firstName: String, lastName: String) {
self.init(firstName: firstName, lastName: lastName, age: nil)
}

init(firstName: String, lastName: String, age: UInt?) {
self.firstName = firstName
self.lastName = lastName
self.age = age
}
}

Dieses Verhalten wird als Initializer Delegation bezeichnet.