Optionals
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.