İçeriğe geç / Skip to content / Zum Inhalt

Einführung in Swift: Optionals sowie guard let und if let

Ahmet Balaman

Zuletzt aktualisiert:

6 Min. Lesezeit

SwiftiOSOptionalsGuard LetSwiftUI
Einführung in Swift: Optionals sowie guard let und if let

Wer mit Swift anfängt, läuft in der ersten Woche gegen dieselbe Wand: Optionals. Der Compiler lässt Sie einen Wert nicht verwenden, der Bildschirm füllt sich mit Value of optional type 'String?' must be unwrapped, und das !, das wie der schnellste Ausweg aussieht, landet überall. Ein paar Wochen später stürzt die App an unerwarteten Stellen ab.

Dieser Beitrag behandelt Optionals nicht als Hindernis, sondern als das stärkste Sicherheitswerkzeug von Swift. Wer von Flutter und Dart kommt, hat Glück: Die meisten Konzepte sind vertraut. Auf die wenigen Unterschiede weise ich gezielt hin. Die Beispiele kompilieren mit aktuellen Swift-Versionen, einschließlich Swift 6.

Was ist ein Optional?

In Swift enthält eine Variable entweder einen Wert oder nichts. Der Fall „nichts“ wird mit nil ausgedrückt, doch damit eine Variable nil sein darf, muss ihr Typ als Optional gekennzeichnet sein. Apples Dokumentation zu Optional beschreibt ihn als Typ, der entweder einen verpackten Wert oder das Fehlen eines Werts darstellt.

var name: String = "Ahmet"   // kann nicht nil sein
var surname: String? = nil   // Optional: kann nil sein
surname = "Balaman"

String? ist eigentlich die Kurzform von Optional<String>, und Optional ist ein Enum mit zwei Fällen: .none (kein Wert) und .some(wert). Dieses Detail hilft später, wenn wir Optionals mit switch behandeln.

Optionals begegnen Ihnen nicht nur in selbst deklarierten Variablen. In der Standardbibliothek liefert jeder Vorgang, der „fehlschlagen kann“, ein Optional:

let age = Int("42")          // Int?  (nil, wenn der Text keine Zahl ist)
let first = [3, 5, 8].first  // Int?  (nil, wenn das Array leer ist)
let capitals = ["TR": "Ankara", "DE": "Berlin"]
let capital = capitals["FR"] // String?  (nil, wenn der Schlüssel fehlt)

Int("42") entspricht Darts int.tryParse: Schlägt die Umwandlung fehl, wird kein Fehler geworfen, sondern nil geliefert.

Ein wichtiger Unterschied für Dart-Umsteiger

In Dart grenzt der Compiler den Typ einer Variable nach einer Null-Prüfung von selbst ein. Swift tut das nicht:

let email: String? = "[email protected]"

if email != nil {
    // print(email.count) // Fehler: email ist immer noch String?
}

In Swift müssen Sie den Wert ausdrücklich auspacken (unwrap), bevor Sie ihn verwenden. Alle folgenden Methoden erledigen genau das.

Methoden zum Auspacken von Optionals

Ablauf beim Auspacken eines Optionals: die Frage nach Wert oder nil, sichere Nutzung mit if let und guard let, sonst der else-Zweig

1. Force Unwrapping (!)

Die kürzeste, aber gefährlichste Methode. Ist der Wert nil, stürzt die App zur Laufzeit ab:

let age: Int? = 25
print(age!)  // 25; wäre age nil, würde die App abstürzen

Das ist die Gewohnheit, die ich im Unterricht am häufigsten korrigiere. Mit ! sagen Sie dem Compiler: „Das ist nie nil, ich übernehme die Verantwortung.“ Dieses Versprechen können Sie nur selten wirklich geben, etwa bei einer Ressourcendatei, die Sie selbst ins Projekt gelegt haben. Verwenden Sie ! nie bei Daten, die vom Nutzer, aus dem Netz oder von der Festplatte kommen.

2. if let: den Wert nutzen, wenn er existiert

let email: String? = "[email protected]"

if let email {
    print("E-Mail: \(email)") // Hier ist email ein normaler String
} else {
    print("Keine E-Mail gefunden")
}

Die Schreibweise if let email { kam mit Swift 5.7 und ist die Kurzform von if let email = email: Im Block entsteht eine ausgepackte Kopie mit demselben Namen. Neue Namen wie if let userEmail = email, wie sie ältere Tutorials oft verwenden, brauchen Sie nicht mehr zu erfinden.

Mehrere Werte und zusätzliche Bedingungen lassen sich in einer Zeile auspacken:

let username: String? = "ahmet"
let age: Int? = 25

if let username, let age, age >= 18 {
    print("\(username) ist volljährig")
}

Der Block läuft nur, wenn alle durch Kommas getrennten Bedingungen erfüllt sind. Das liest sich weit besser als verschachtelte if let-Blöcke (die Struktur, die Lernende „Pyramide“ nennen).

3. guard let: früh aussteigen, wenn der Wert fehlt

func printUserInfo(name: String?, age: Int?) {
    guard let name, let age else {
        print("Name und Alter sind erforderlich")
        return
    }

    // Ab hier sind name und age ausgepackt und nutzbar
    print("\(name) - \(age) Jahre alt")
}

printUserInfo(name: "Ahmet", age: 25) // Ahmet - 25 Jahre alt
printUserInfo(name: nil, age: 25)     // Name und Alter sind erforderlich

Der else-Block eines guard muss den aktuellen Gültigkeitsbereich verlassen (return, throw, break oder continue); der Compiler prüft das. Im Gegenzug stehen die ausgepackten Werte nach der guard-Zeile im restlichen Teil der Funktion zur Verfügung. Am häufigsten werden Sie dieses Muster bei Netzwerkaufrufen mit async/await einsetzen, wo fehlende Felder in Serverdaten mit guard let einzeln aussortiert werden.

if let oder guard let?

Auch wenn beide dasselbe zu tun scheinen, lenken sie die Form Ihres Codes in verschiedene Richtungen.

if let passt, wenn es mit oder ohne Wert weitergeht. Gibt es einen Wert, geschieht das eine, sonst das andere:

let discounts: [String: Double] = ["SOMMER10": 10, "STUDENT": 25]

func finalPrice(total: Double, couponCode: String?) -> Double {
    if let couponCode, let discount = discounts[couponCode] {
        return total - discount
    }
    return total // Normalpreis, wenn kein oder ein ungültiger Gutschein vorliegt
}

print(finalPrice(total: 100, couponCode: "SOMMER10")) // 90.0
print(finalPrice(total: 100, couponCode: nil))        // 100.0

guard let passt, wenn die Funktion ohne den Wert nicht weitermachen kann. Zuerst werden die Vorbedingungen geprüft, die eigentliche Arbeit steht am Ende ohne zusätzliche Einrückung:

func charge(card: String, amount: Double) {
    print("\(amount) € von Karte \(card) abgebucht")
}

func processPayment(amount: Double?, cardNumber: String?) {
    guard let amount, amount > 0 else {
        print("Ein gültiger Zahlungsbetrag ist erforderlich")
        return
    }

    guard let cardNumber, !cardNumber.isEmpty else {
        print("Eine Kartennummer ist erforderlich")
        return
    }

    charge(card: cardNumber, amount: amount)
}

Eine praktische Regel: Sobald am Anfang einer Funktion mehrere if let-Blöcke ineinander verschachtelt werden, liest sich der Code mit guard let fast immer besser. Der „Happy Path“ (das Szenario, in dem alles gut geht) bleibt linksbündig und ohne Unterbrechung.

Der Nil-Coalescing-Operator (??)

Er legt einen Standardwert fest, der verwendet wird, wenn der Wert nil ist. Er entspricht ?? in Dart:

let userName: String? = nil
let displayName = userName ?? "Gast"

print(displayName) // Gast

In UI-Code wird er viel genutzt: „Gast“, wenn kein Benutzername da ist, ein Standardbild, wenn kein Profilfoto existiert. Dictionaries haben zusätzlich den Parameter default:: stock["apfel", default: 0] liefert 0, wenn der Schlüssel fehlt. Wie ein optionaler Wert an den Bildschirm gebunden wird, sehen Sie im Beitrag über Datenfluss mit @State und @Binding in SwiftUI.

Optional Chaining

Mit ?. bewegen Sie sich sicher durch verschachtelte Optionals:

struct Address {
    var city: String?
}

struct Profile {
    var address: Address?
}

struct User {
    var profile: Profile?
}

let user = User(profile: Profile(address: nil))
let cityName = user.profile?.address?.city ?? "Unbekannt"
print(cityName) // Unbekannt

Ist irgendein Glied der Kette nil, wird der ganze Ausdruck nil, und ?? greift. Das ist dieselbe Logik wie ?. in Dart. In einer früheren Fassung dieses Beitrags waren diese Modelle als class geschrieben; in Swift nimmt man für Datenmodelle meist struct. Warum, erkläre ich im Beitrag über den Unterschied zwischen struct und class.

Chaining funktioniert nicht nur mit Eigenschaften, sondern auch mit Methoden: user.profile?.address?.city?.uppercased() liefert entweder einen String oder nil.

Optionals mit switch behandeln

Da Optional ein Enum ist, können Sie es auch mit switch behandeln. Das ist gut lesbar, wenn Sie den Fall ohne Wert und den Fall mit Wert getrennt behandeln wollen:

let score: Int? = 85

switch score {
case nil:
    print("Noch keine Note eingetragen")
case let value? where value >= 50:
    print("Bestanden: \(value)")
case let value?:
    print("Nicht bestanden: \(value)")
}

Das Muster case let value? passt, indem es den Wert auspackt; mit where kommt eine zusätzliche Bedingung hinzu.

Häufige Fehler

  • ! ergänzen, um einen Compilerfehler zum Schweigen zu bringen; der Fehler verschwindet nicht, er wird auf die Laufzeit verschoben.
  • Nach einer Prüfung if x != nil dann x! schreiben; nehmen Sie stattdessen if let x.
  • Verschachtelte if let-Blöcke bauen; verbinden Sie sie mit Kommas oder nutzen Sie guard let.
  • Bei jedem if let einen neuen Namen erfinden; die kurze Form if let x { genügt.
  • Jedes Feld eines Datenmodells optional machen; ist ein Wert immer vorhanden, sollte er kein Optional sein, sonst müssen Sie überall im Code auspacken.

Nächster Schritt

Sitzen die Optionals, geht es natürlich mit der Oberfläche weiter: die erste App mit SwiftUI. Möchten Sie diese Grundlagen mit einer Lehrkraft erarbeiten, erklärt der Beitrag So finden Sie den besten Swift-Lehrer, worauf Sie achten sollten.

Ressourcen


Wenn Sie mit mir arbeiten möchten:

Häufig gestellte Fragen

Was ist die Kurzform if let email { }?

Eine mit Swift 5.7 eingeführte Kurzschreibweise: Statt if let email = email schreiben Sie if let email {; das erzeugt eine ausgepackte Kopie mit demselben Namen. Sie funktioniert auch bei guard let email else { return }.

Wann verwende ich ein implizit ausgepacktes Optional wie String!?

Nur wenn Sie sicher wissen, dass der Wert direkt nach dem Erzeugen des Objekts gesetzt wird; das klassische Beispiel sind @IBOutlet-Eigenschaften in UIKit. In SwiftUI-Code brauchen Sie es so gut wie nie; im Zweifel nehmen Sie ein normales ?.

Gibt es einen Unterschied zwischen if x != nil und if let?

if x != nil prüft nur, ob ein Wert existiert; im Block ist x weiterhin ein Optional und muss vor der Nutzung ausgepackt werden. Wollen Sie den Wert verwenden, greifen Sie zu if let oder guard let.

Sind Swift-Optionals dasselbe wie nullable Typen in Dart?

Die Idee ist dieselbe: Ein Typ ohne ? kann nicht nil (in Dart null) sein, und beide Sprachen kennen ?? und ?.. Der entscheidende Unterschied: Dart grenzt eine Variable nach einer Null-Prüfung von selbst ein, während Swift verlangt, dass Sie den Wert ausdrücklich mit if let oder guard let auspacken.

Kommentare