Über den Sinn und Unsinn von Erweiterungsmethoden, den sogenannten Extensions, habe ich mit meinen Kollegen oft diskutieren müssen. Viele der Windows-Entwickler meiden sie. In der Apple-Welt ist man zum Glück etwas aufgeschlossener. Ich persönlich halte Extensions für eine tolle Sache. Statt von Klassen erben zu müssen können einer bestehenden Klasse zusätzliche Funktionen hinzugefügt werden. Wenn man diese Vorgehensweise verfolgt, entwickelte sich automatisch eine Bibliothek von Funktionen, die viele Aufgaben des Programmieralltags abdeckt und die jederzeit wiederverwendet werden kann.
Extension können auf zwei Arten definiert werden. Als eine zusätzliche Funktion für eine Klasse, oder, wenn die Situation es erlaubt, als eine berechnete Eigenschaft. Zusätzliche echte Eigenschaften können einer Klasse durch eine Erweiterungsmethode nicht hinzugefügt werden. Es fehlt die Möglichkeit, den Wert im Speicher abzulegen. In diesem einem Fall wäre dann doch eine Vererbung nötig. Erweiterungsmethoden funktionieren in Swift mit Klassen und Strukturen. Zum Beispiel für den Typ Int, wie das folgende Beispiel zeigt.
extension Int {
// Eine Extension als Funktion
func squaredBy(power :Int) -> Int {
return Int( pow( Double(self) , Double(power) ))
}
// Eine Extension als berechnete Eigenschaft
var squared: Self { self * self }
}
Beim Aufruf unterscheidet sich eine Erweiterungsmethode nicht von den Funktionen, die bei der Klasse oder Struktur von Apple bereitgestellt werden. Es ist am Programmcode nicht zu erkennen.
let value : Int = 5
print(value.squaredBy(power: 5))
print(value.squared)
Wenn der Typ es erlaubt, können Extensions nicht nur auf Variablen und Konstanten angewendet werden, sondern auch auf Literale. Also Werte, die fest im Programmcode stehen.
print(5.squaredBy(power: 5))
print(5.squared)
Eine Extension für einen Optional, also einen Wert der auch nil sein kann, sieht anders aus. In der Definition muss statt dem Wert das Schlüsselwort Optional angegeben werden gefolgt von where Wrapped und dem Typ des Wertes, wenn er nicht nil ist. Im folgenden Beispiel ist es wieder ein Int. Unter den Begriffen »wrap« und »unwrap« versteht man den Zugriff auf den wahren Werte eines Optional. Ein »unwrap«, leicht zu übersetzen mit »auspacken«, funktioniert jedoch nur, wenn es einen Wert gibt und der Zustand eben nicht nil ist. Mit einer guard-Anweisung kann das leicht überprüft werden. Im Beispiel wird der Optional-Int zu dem Nicht-Optional unwrappedint »ausgepackt«. Funktioniert das nicht, gibt die Funktion ihrerseits ein nil zurück.
extension Optional where Wrapped == Int {
// Eine Extension als Funktion
var squared: Self {
guard let unwrappedInt = self else {
return nil
}
return unwrappedInt * unwrappedInt
}
// Eine Extension als berechnete Eigenschaft
func squaredBy(power :Int) -> Int? {
guard let unwrappedInt = self else {
return nil
}
return Int( pow( Double(unwrappedInt) , Double(power) ))
}
}
Um die Extension zu testen, erzeugen wir ein Array aus ganzen Zahlen. Dabei machen wir uns den Umstand zu Nutze, dass ein Int aus einer Zeichenkette erzeugt werden kann. Das funktioniert jedoch nur, wenn eine Umwandlung tatsächlich möglich ist. Die Funktion compactMap ist ähnlich einer For-Schleife, die für jeden Wert Code ausführt, sofern er nicht nil ist. Die Ausgabe der folgenden Anweisungen ist daher 1, 4, 9, 16, 25, 36.
let values = [1, 2, 3, 4, Int("5"), 6]
print(values.compactMap({ $0?.squared }))
Verändern wir den Wert „5" zu „fünf" kann Swift die Zeichenkette nicht mehr automatisch in einen numerischen Typ umwandeln. Dieser Wert im Array wird zu nil. Die compactMap-Funktion ignoriert bei der Verarbeitung jedoch sämtliche nil-Werte und die Ausgabe wird zu 1, 4, 9, 16, 36.
let values = [1, 2, 3, 4, Int("fünf"), 6]
print(values.compactMap({ $0?.squared }))
Eine Extension für einen Optional muss nicht zwangsläufig für genau einen Typ sein. Ohne die Angabe von where Wrapped funktioniert die Erweiterungsmethode dann mit allen Typen, die nil sein können.
extension Optional {
func stringValueOrNilText() -> String {
switch self {
case .some(let value):
return String(describing: value)
case _:
return "Kein Wert - Ist nil"
}
}
}
let a : Int? = nil
let b : String? = nil
print(a.stringValueOrNilText())
print(b.stringValueOrNilText())
Geschrieben am: 13.04.2021 Technologien: Swift, iOS, macOS