26. November 2018

Optionals in Swift

Eine Besonderheit bei der Programmierung in Swift ist, dass Variablen standardmäßig nicht ohne Wert (nil) sein dürfen:

// Compilerfehler: Nil cannot initialize specified type
var str : String = nil

Mit einem Fragezeichen wird die Typangabe als optional deklariert:

var str : String?

Dies bewirkt, dass der eigentliche String-Wert in einem Optional<String> verpackt wird:

Mit dem Ausrufezeichen-Operator wird das eigentliche Objekt wieder „ausgepackt“:

str.append(" world")         // Compilerfehler: Value of optional type 'String?' has no member 'append'

str!.append(" world")

Dabei wird von einem gesetzten Wert ausgegangen, andernfalls führt diese Operation zu einem Laufzeitfehler „unexpectedly found nil while unwrapping an Optional value“. Die gegebenenfalls notwendige Prüfung auf nil kann manuell mit einem if-Block erfolgen:

if str != nil {
    str!.append(" world")
}

Diese Operation kann mit dem Fragezeichen-Operator verkürzt geschrieben werden, um die Methode nur dann aufzurufen, wenn ein Wert gesetzt ist:

str?.append(" world")

Eine weitere Möglichkeit der Prüfung auf nil ist eine Zuweisung als Optional Binding mit if let / if var, um auf nil zu prüfen und den eigentlichen Wert auszupacken. Dies ist vor allem dann sinnvoll, wenn mehrere Operationen auf dem Objekt erfolgen sollen:

if var actualStr = str {
    actualStr.append(" ")
    actualStr.append("world")
    actualStr.append("!")
}

Die explizite Deklaration von optionalen Typen hilft bei der frühzeitigen Erkennung von Programmierfehlern. So ist zum Beispiel für Methodenparameter explizit festgelegt, ob nil als Parameterwert erlaubt ist. Der Compiler kann diese Regeln prüfen und entsprechende Fehlermeldungen erzeugen. Zudem wird damit das Typsystem hinsichtlich der Verwendung von primitiven Typen wie Int und Objektreferenzen vereinheitlicht - beides kann ohne Wert sein, sofern die Deklaration entsprechend erfolgt:

var url : URL?
var number : Int?

Tutorial 1: Optionals

  1. Erstellen Sie mit File » New » Playground einen neuen Playground oder verwenden Sie den Playground aus der Swift-Einführung.

  2. Deklarieren Sie eine Liste mit Wörtern und holen Sie mit der first-Eigenschaft das erste Element der Liste:

    let words = ["bird", "tree", "house"]
    let firstWord = words.first
    print(firstWord)
  3. Verwenden Sie die Xcode-Codevervollständigung um sich den Typ der first-Eigenschaft anzeigen zu lassen sowie -Klick um den Typ von firstWord anzuzeigen:

    Optionaler Rückgabewert von Array.first

    Typ für Variablendeklaration
  4. Rufen Sie eine Methode, z.B. uppercased auf, ohne das Optional zu berücksichtigen:

    print(firstWord.uppercased())

    Sie werden einen Fehler erhalten:

    Playground execution failed: error: MyPlayground.playground:
    error: value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?
  5. Beheben Sie das Problem, indem Sie den Wert zunächst mit dem !-force unwrap-Operator auspacken und in einer Konstante ablegen:

    let unpackedWord = firstWord!
    print(unpackedWord.uppercased())
  6. Deklarieren Sie die Liste als leere Liste, um das Verhalten zu prüfen, wenn kein Listenelement vorliegt:

    let words : [String] = []

    Sie werden einen fatal error beobachten:

    fatal error: unexpectedly found nil while unwrapping an Optional value
  7. Verwenden Sie zunächst eine gewöhnliche if-Bedingung um diesen Fall zu behandeln:

    let firstWord = words.first
    if firstWord != nil {
        let unpackedWord = firstWord!
        print(unpackedWord.uppercased())
    } else {
        print("Kein Wert")
    }
  8. Verwenden Sie alternativ die Optional Binding-Kurzsyntax if let:

    if let firstWord = words.first {
        print(firstWord.uppercased())
    } else {
        print("Kein Wert")
    }
  9. Verwenden Sie alternativ die Kurzschreibweise mit dem ? Operator, bei dem der folgende Ausdruck nil liefert falls der Wert nil ist:

    print(firstWord?.uppercased())
  10. Verwenden Sie zusätzlich den ??-Operator um einen Fallback-Wert für diesen Fall anzugeben:

    print(firstWord?.uppercased() ?? "Leere Liste")