Optionals

von @ralfebert · aktualisiert am 4. November 2021
Xcode 13 & Swift 5.5
Swift Kurzreferenz

Für die Handhabung von optionalen Werten gibt es in der Swift Standard Library einen Enum-Typ der etwa folgendermaßen definiert ist:

enum Optional<Value> {
    case none
    case some(Value)
}

Damit können bei der Definition eigener Typen Werte als optional deklariert werden. Ansonsten ist Swift sehr streng bei der Verwendung von Typen, d.h. es gibt abgesehen von den Optionals keinen Null-Pointer oder Ähnliches. Mit den Optionals könnte etwa ein Attribut in einem Datentyp optional gemacht werden:

struct Person {
      var name: String
      var age: Optional<Int>
}

Während name immer gesetzt sein muss, kann age nun auch ohne Wert sein:

let alice = Person(name: "Alice", age: .some(32))
let bob = Person(name: "Bob", age: .none)

Diese Schreibweise veranschaulicht zwar den zugrundeliegenden Mechanismus, in der Praxis wird jedoch immer folgende Kurzschreibweise verwendet:

struct Person {
      var name: String
      var age: Int?
}

let alice = Person(name: "Alice", age: 32)
let bob = Person(name: "Bob", age: .none)

(Statt .none könnte auch nil verwendet werden).

Optionals behandeln

switch

Für die Behandlung von Fällen, in denen ein Wert optional sein kann, stellt Swift komfortable Sprachmittel bereit. Am explizitesten wäre eine Prüfung mit einem switch-Statement:

switch(person.age) {
case .none:
    print("We don't know")
case .some(let age):
    print("Person is \(age) years old")
}

Optional Binding: if let

Diese Prüfung kann mit einem if let-Statement verkürzt geschrieben werden:

if let age = person.age {
    print("Person is \(age) years old")
} else {
    print("We don't know")
}

Dabei können auch mehrere Werte gleichzeitig geholt und das Statement kann mit einer regulären if-Prüfung kombiniert werden, beispielsweise:

if let ageAlice = alice.age, let ageBob = bob.age, ageAlice > ageBob {
    print("Alice is older than Bob")
}

?-Operator

Geht es nur darum, auf einen einzelnen Wert in einem Optional zuzugreifen, kann der ?-Operator verwendet werden:

struct Person {
      var name: String
      var age: Int
}

struct Company {
    var manager: Person?
}

let company = Company(manager: Person(name: "Bob", age: 32))
let managerAge = company.manager?.age

Obwohl age im obigen Beispiel nicht optional definiert ist, ist der Rückgabetyp dieser Operation jedoch wieder ein Optional, da der Zugriff über das optionale manager-Attribut erfolgt. Ggf. macht der Einsatz zusammen mit einem if let Sinn:

if let age = company.manager?.age {
    print("The manager is \(age) years old")
}

Fallback: ??-Operator

Eine weitere Möglichkeit einen optionalen Wert zu behandeln wäre ein Fallback-Wert zu definieren, der verwendet werden soll, wenn kein Wert gesetzt ist:

let managerAge = company.manager?.age ?? 0

Force unwrapping: !-Operator

Mit dem !-Operator kann ein optionaler Wert unmittelbar „ausgepackt“ werden. Ist dabei kein Wert gesetzt, kommt es zu einem Crash. Das heißt, die Verwendung dieses Operators sollte Fällen vorbehalten bleiben, in denen 100% sicher ist, dass der .none-Fall nicht auftreten kann:

// This will crash when no age is set
let managerAge = company.manager!.age

Selbst in diesem Fall macht es Sinn, diesen Fall explizit zu machen, selbst wenn das Verhalten "Crash beim Wert .none" gewünscht ist:

if let age = company.manager?.age {
    print("The manager is \(age) years old")
} else {
    fatalError("Manager has no age.")
}

Motivation

Die explizite Deklaration von optionalen Werten macht Schnittstellenkontrakte klarer und hilft bei der frühzeitigen Erkennung von Programmierfehlern. So ist zum Beispiel für Funktionen explizit festgelegt, ob .none als Parameterwert erlaubt ist bzw. ob immer oder nur in bestimmten Situationen ein Wert zurückgegeben wird. Der Compiler kann diese Regeln prüfen und erzwingt eine explizite Behandlung dieser Situationen.

Weitere Informationen