Init-Funktionen in Swift

Die Init-Funktionen (Initializer) dienen dazu die Eigenschaften (Properties) eines neuen Datentyps zu initialisieren. In anderen Programmiersprachen kennt man sie unter der Bezeichnung Konstruktoren.

Init-Funktionen in Klassen

In einer Klasse ist eine Init-Funktion immer dann erforderlich, wenn den Variablen keine Werte zugeordnet worden sind. Im folgenden Beispiel bedarf es also keiner Init-Funktion:

class Person {
    let name: String = "Peter"
    let age: Int = 34
}

Bei folgendem Code würde sich der Compiler aber beschweren:

class Person {
    let name: String
    let age: Int
}

Es erscheint die Fehlermeldung: „Class ‘Person’ has no initializers“. Da keine Standardwerte für die Eigenschaften name und age angegeben wurden, bedarf es hier einer Init-Funktion, so wie im folgenden Beispiel gezeigt:

class Person {
    let name: String
    let age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

Wir Ihr sehen könnt, kommt in der Init-Funktion das Schlüsselwort self vor. Es verweist auf den Namen der Eigenschaft (Property), also name und age.

In vielen Fällen bedarf es des Schlüsselwortes self nicht, denn der Compiler ist häufig in der Lage zu erkennen, was gemeint ist. Dies gilt grundsätzlich auch für Init-Funktionen. Aufgrund der Gleichheit von Eigenschaftsnamen und Parameternamen, muss hier aber self verwendet werden. Anders wäre es hingegen, wenn ich die Init-Funktion wie folgt geschrieben hätte:

init(a: String, b: Int) {
    name = a
    age = b
}

Bei der bisher gezeigten Init-Funktion handelt es sich um einen sogenannten Designated Initializer (auch: Designated Init Function). Damit ist eine Init-Funktion gemeint, mit der alle Eigenschaften initialisiert werden.

Eine Klasse kann (muss aber nicht) weitere Init-Funktionen enthalten. Weitere Init-Funktionen werden als Convenience Initializer bezeichnet. Sie rufen mit self.init den designated initializer auf und dienen dazu, eine Instanz mit Standardwerten zu initialisieren:

class Person {

    let name: String
    let age: Int

    // designated initializer
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    // convenience initializer
    convenience init(name: String) {
        self.init(name: name, age: 27)
    }
}

Dies führt dazu, dass bei der Initialisierung von Person zwei Init-Funktionen angeboten werden. Zum einen könnte ich eine Personen-Instanz erstellen, bei der nur der Name angegeben wird. Für age wird der Standardwert 27 verwendet:

let p = Person(name: "Kendra")

Zum anderen könnte eine Instanz mit Name und Alter erstellt werden:

let p = Person(name: "Tom", age: 20)

Init-Funktionen in Strukturen

Betrachten wir folgendes Beispiel:

struct Rectangle {
    var a: Double
    var b: Double
}

let r = Rectangle(a: 4.0, b: 7.5)
r.a // -> 4
r.b // -> 7.5

Obwohl hier den Variablen keine Werte zugeordnet wurden, beschwert sich der Compiler nicht (anders als dies bei einer Klasse der Fall gewesen wäre). Das hängt damit zusammen, dass bei Strukturen automatic initializers (auch: memberwise initializer) zum Einsatz kommen, mit der eine neue Instanz erzeugt werden kann.

Es besteht aber die Möglichkeit, eigene Init-Funktionen zu schreiben. Sobald man das macht, wird keine automatische Init-Funktion verwendet:

struct Rectangle {
    var a: Double
    var b: Double

    init(a: Double, b: Double) {
        self.a = a
        self.b = b
    }
}

Wie bei den Referenztypen (also Klassen), können auch bei Strukturen mehrere Init-Funktionen geschrieben werden:

struct Staatsoberhaupt {
    let firstName: String
    let lastName: String

    init() {
        firstName = "Frank-Walter"
        lastName = "Steinmeier"
    }

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

Die Zeile

init(firstName: String, lastName: String)

ermöglicht hier, bei der Initialisierung einen anderen Namen anzugeben.

Was gibt es noch?

Euch werden auch noch andere Init-Funktionen begegnen. Da wären z.B. die Required Init Functions und die Failable Init Functions, die ich in diesem Beitrag lediglich kurz erwähnen möchte.

Required Initializer

Der Verwendung des Modifizierers required bei einer Init-Funktion führt dazu, das alle Subklassen diesen Initialisierer implementieren müssen.

class Employee {

    let placeOfBirth: String

    // required init function
    required init?(placeOfBirth: String) {
        self.placeOfBirth = placeOfBirth
    }
}

Würde man nun eine Subklasse von Employee schreiben, würde die Verwendung einer „schlichten“ Init-Funktion den Compiler zu einer Fehlermeldung veranlassen: „required initializer must be provided by subclass of…“.

Failable Initializer

Eine Init-Funktion kann mit einem Fragezeichen als Optional gekennzeichnet werden. Als Beispiel greife ich hier auf eine Abwandlung des Beispiels aus Apples Swift-Dokumentation zurück:

class Developer {

    var age: Int!

    // failable init function
    init?(age: Int) {
        self.age = age
        if age < 22 {
            return nil
        }
    }
}

let d = Developer(age: 17) // -> nil

Wie das Beispiel verdeutlicht, kann damit überprüft werden, ob ein übergebener Parameter gewünscht ist. Hier soll age größer als „22“ sein, andernfalls wird nil zurückgegeben.

Im iOS-Framework wird von failable initializers intensiven Gebrauch gemacht. Ein Beispiel ist UIImage(named). Die init-Methode ist definiert als init?(named:). Diese Methode gibt also ein Optional zurück, das entpackt werden muss. Sollte es beim Laden des Bildes ein Problem geben, wird nil zurückgegeben, andernfalls das Bild.

Deinit-Funktion

Und dann wäre da noch die – nur bei Klassen vorhandene – Deinit-Funktion. Sie wird kurz vor der Entfernung einer Instanz aus dem Speicher ausgeführt. Damit könnte man beispielsweise überprüfen, ob eine Instanz tatsächlich entfernt wurde:

deinit {
    print("Info: (person) is being deinitialized!")
}