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!")
}