GO FURTHER, FASTER: Try the Swift Career Accelerator today! >>

Optionals

Swift ist eine sehr sichere Sprache, was heißen soll, dass sie alles dafür tut, dass Dein Code niemals auf unvorhergesehene Weise abstürzt.

Einer der häufigsten Gründe, warum Code abstürzt, ist, wenn er versucht, auf Daten zuzugreifen, die entweder kaputt sind oder fehlen. Betrachte beispielsweise folgende Funktion:

func getHaterStatus() -> String {
    return "Hate"
}

Diese Funktion nimmt keine Parameter entgegen und gibt den String "Hate" zurück. Was aber, wenn heute ein ziemlich sonniger Tag ist und den Hatern gar nicht nach Hassen zumute ist - was dann? Nun, dann wollen wir vielleicht auch einfach nichts zurückgeben: Dieser Hater wird heute nicht haten.

Bei Strings kann man sich noch überlegen, dass ein leerer String die beste Art und Weise ist, nichts zu kommunizieren und das mag manchmal auch stimmen. Aber was ist mit Zahlen - ist 0 eine "leere Zahl"? Oder -1?

Bevor Du anfängst, Dir selbst eigene Regeln auszudenken, bietet Swift eine Lösung: Optionals. Ein Wert, der optional ist, ist einer, der einen Wert haben könnte oder eben nicht. Die meisten Leute finden Optionals eher unverständlich und das ist in Ordnung - ich werde versuchen, sie auf verschiedene Weisen zu erklären, von denen hoffentlich eine funktioniert.

Für den Moment, stelle Dir eine Umfrage vor, in der Du jemanden fragst "Auf einer Skala von 1 bis 5, wie super ist Taylor Swift?" - was würde jemand antworten, der nie von ihr gehört hat? 1 würde sie zu Unrecht verreißen und 5 würde sie in den Himmel loben, obwohl derjenige keine Ahnung hat, wer Taylor Swift eigentlich ist. Die Lösung sind Optionals: "Ich möchte keine Zahl liefern."

Als wir -> String geschrieben haben, bedeutete dies "das hier wird definitiv einen String liefern", was bedeutet, dass diese Funktion niemals keinen Wert liefern kann. Daher kann sie mit großer Sicherheit aufgerufen werden, in dem Wissen, dass man immer einen Wert zurück bekommen wird, den man als String benutzen kann. Würden wir Swift mitteilen wollen, dass diese Funktion einen Wert zurückgeben kann, oder eben auch nicht, benutzen wir stattdessen das hier:

func getHaterStatus() -> String? {
    return "Hate"
}

Man beachte das zusätzliche Fragezeichen: String? bedeutet "optional String". Nun werden wir in unserem Fall immer noch "Hate" zurück geben, egal was passiert, aber lass uns unsere Funktion noch weiter abändern: Wenn das Wetter sonnig ist, haben die Hater ein neues Kapitel aufgeschlagen und ihr Leben voller Hass aufgegeben, also wollen wir keinen Wert zurückgeben. In Swift hat "kein Wert" einen speziellen Namen: nil.

Ändere die Funktion folgendermaßen:

func getHaterStatus(weather: String) -> String? {
    if weather == "sunny" {
        return nil
    } else {
        return "Hate"
    }
}

Dies nimmt einen String-Parameter entgegen (das Wetter) und gibt einen String zurück (den Status des Haters), aber der Rückgabewert kann da sein oder eben nicht - dann ist er nil. In diesem Fall bedeutet das, dass wir einen String kriegen könnten oder nil.

Nun der wichtige Teil: Swift will, dass unser Code wirklich sicher ist und nil als Wert zu benutzen, ist eine schlechte Idee. Das könnte nämlich unseren Code zum Absturz bringen oder unsere App-Logik stören oder unser User-Interface zeigt die falschen Dinge. Im Ergebnis heißt das: Falls Du einen Wert als optional erklärst, wird Swift dafür sorgen, dass Du auf sichere Art damit umgehst.

Lass uns das ausprobieren: Füge diese Code-Zeilen am Ende Deines Playgrounds hinzu:

var status: String
status = getHaterStatus(weather: "rainy")

Die erste Zeile erstellt eine String-Variable und die zweite setzt sie auf den Wert von getHaterStatus() - und heute ist das Wetter regnerisch, also werden diese Hater ganz sicher haten.

Der Code wird nicht laufen, weil wir gesagt haben, dass status vom Typ String ist, was einen Wert erfordert. Aber getHaterStatus() könnte keinen liefern, weil sie nur einen Optional String zurückgibt. Mit anderen Worten: Wir haben gesagt, dass in status definitiv ein String stehen wird, aber getHaterStatus() könnte auch einfach nichts zurückliefern. Swift wird Dich diesen Fehler schlicht nicht machen lassen, was sehr hilfreich ist, weil das im Endeffekt eine ganze Sorte von gängigen Bugs im Keim erstickt.

Um das Problem zu kösen, müssen wir die status Variable zu einem String? umwandeln, oder eben die Typ-Annotation ganz entfernen und Swift den Typ inferieren lassen. Die erste Option sieht folgendermaßen aus:

var status: String?
status = getHaterStatus(weather: "rainy")

Und die Zweite folgendermaßen:

var status = getHaterStatus(weather: "rainy")

Egal, welche davon Du wählst, der Wert könnte da sein oder eben nicht und Swift wird von sich aus nicht zulassen, dass Du ihn auf eine gefährliche Art und Weise verwendest. Als Beispiel, stelle dir folgende Funktion vor:

func takeHaterAction(status: String) {
    if status == "Hate" {
        print("Hating")
    }
}

Diese nimmt einen String und gibt eine Nachricht aus, je nachdem, was in ihm drin steht. Diese Funktion nimmt einen String-Wert - Du kannst hier kein Optional rein geben, sie will einen echten String, was heißt, dass wir sie mit der status Variable nicht aufrufen können.

Swift hat zwei Lösungen. Beide werden verwendet, aber die eine von ihnen wird definitiv bevorzugt. Die erste wird Optional Umwrapping genannt und wird mittels einer speziellen Syntax in einer Bedingung verwendet. Es tut zwei Dinge gleichzeitig: Schaut, ob ein Optional einen Wert besitzt und falls ja, packt sie diesen aus (im Englischen "unwrap" genannt), speichert ihn in einem nicht-optionalen Typen und führt dann einen Code-Block aus.

Die Syntax sieht folgendermaßen aus:

if let unwrappedStatus = status {
    // unwrappedStatus besitzt einen nicht-optionalen Wert!
} else {
    // Falls Du einen else-Teil willst, hier kann er rein
}

Diese if let Befehle prüfen und unwrappen in einer prägnanten Zeile, was sie sehr gebräuchlich macht. Mittels dieser Methode können wir auf sichere Weise den Rückgabewert von getHaterStatus() unwrappen und sicher sein, dass wir takeHaterAction() nur mit einem validen, nicht-optionalen String aufrufen. Hier ist der komplette Code:

func getHaterStatus(weather: String) -> String? {
    if weather == "sunny" {
        return nil
    } else {
        return "Hate"
    }
}

func takeHaterAction(status: String) {
    if status == "Hate" {
        print("Hating")
    }
}

if let haterStatus = getHaterStatus(weather: "rainy") {
    takeHaterAction(status: haterStatus)
}

Wenn Du das Konzept verstanden hast, darfst Du den Abschnitt bis zum Titel "Force-Unwrapping von Optionals" überspringen. Falls Du aber noch nicht ganz sicher bist, was Optionals angeht, lies ruhig weiter.

OK, wenn Du immer noch hier bist, heißt das, dass meine Erklärung oben entweder keinen Sinn ergeben hat oder dass Du schon was verstanden, aber noch Klärungsbedarf hast. Optionals werden in Swift sehr häufig gebraucht, also solltest Du sie wirklich verstehen. Ich werde versuchen, es nochmal auf eine andere Weise zu erklären und ich hoffe, das wird helfen!

Hier ist eine neue Funktion:

func yearAlbumReleased(name: String) -> Int {
    if name == "Taylor Swift" { return 2006 }
    if name == "Fearless" { return 2008 }
    if name == "Speak Now" { return 2010 }
    if name == "Red" { return 2012 }
    if name == "1989" { return 2014 }

    return 0
}

Dies nimmt den Namen eines Taylor Swift Albums und gibt das Jahr zurück, in dem es erschienen ist. Aber falls wir sie mit dem Album-Namen "Lantern" aufrufen, weil wir Taylor Swift mit Hudson Mohawke verwechselt haben (kann ja mal passieren, oder?), dann würde sie 0 zurück geben, weil es kein Album von Taylor ist.

Aber ist 0 hier wirklich sinnvoll? Ja, falls das Album im Jahre 0 erschienen wäre, als Caesar Augustus der römische Kaiser war, dann wäre 0 sinnvoll, aber hier verwirrt das einfach nur - die Leute müssten vorher wissen, dass 0 in dem Fall "nicht erkannt" bedeutet.

Eine viel bessere Idee ist es, die Funktion umzuschreiben, sodass sie entweder eine Zahl zurück liefert (wenn das Jahr gefunden wurde), oder nil (falls nichts gefunden wurde), was dank Optionals recht einfach geht. Hier ist die neue Funktion:

func yearAlbumReleased(name: String) -> Int? {
    if name == "Taylor Swift" { return 2006 }
    if name == "Fearless" { return 2008 }
    if name == "Speak Now" { return 2010 }
    if name == "Red" { return 2012 }
    if name == "1989" { return 2014 }

    return nil
}

Nun, da sie nil zurückgibt, müssen wir das Ergebnis mit if let unwrappen, weil wir nachschauen müssen, ob der Wert existiert oder nicht.

Falls Du das Konzept jetzt verstehst, darfst Du gerne beim Abschnitt "Force-Unwrapping von Optionals" weiter machen. Falls Du Dir immer noch unsicher bist, was Optionals angeht, lies ruhig weiter.

OK, wenn Du immer noch hier bist, bedeutet das, dass Du noch echte Probleme mit Optionals hast, also versuche ich noch ein letztes Mal, sie zu erklären.

Hier ist eine Liste von Namen:

var items = ["James", "John", "Sally"]

Falls wir eine Funktion schreiben wollen, die in die Liste schaut und uns den Index eines bestimmten Namens mitteilt, könnten wir sowas hier schreiben:

func position(of string: String, in array: [String]) -> Int {
    for i in 0 ..< array.count {
        if array[i] == string {
            return i
        }
    }

    return 0
}

Dies geht alle Elemente der Liste durch und gibt die Position zurück, falls etwas gefunden wird, andernfalls 0.

Versuche nun, folgende vier Zeilen Code auszuführen:

let jamesPosition = position(of: "James", in: items)
let johnPosition = position(of: "John", in: items)
let sallyPosition = position(of: "Sally", in: items)
let bobPosition = position(of: "Bob", in: items)

Dies wird 0, 1, 2, 0 zurück geben, die Positionen von James und Bob sind die gleichen, obwohl der eine Name vorhanden ist und der andere nicht. Das passiert, weil ich 0 als "nicht gefunden" benutzt habe. Die einfache Lösung wäre, -1 als nicht gefunden zu deklarieren, aber man müsste immer noch wissen, welche Zahl "nicht gefunden" bedeutet.

Die Lösung sind Optionals: Gebe eine Zahl zurück, wenn Du etwas gefunden hast und nil falls nicht. Tatsächlich ist das der exakt gleiche Ansatz, den die eingebauten "suche in Liste"-Methoden verwenden: someArray.index(of: someValue).

Wenn Du mit diesen "Kann da sein, kann aber auch nicht da sein"-Werten arbeitest, zwingt Swift Dich, diese zu unwrappen, bevor Du sie benutzen kannst, in dem Wissen, dass da auch kein Wert stehen kann. Das ist das, was die if let-Syntax tut: Wenn das Optional einen Wert hat, unwrappe und benutze ihn oder benutze ihn einfach gar nicht. Du kannst keinen vielleicht-leeren Wert aus Versehen benutzen, weil Swift Dich nicht lässt.

Wenn Du immer noch nicht weißt, wie Optionals funktionieren, dann ist es das Beste, mich bei Twitter (auf Englisch) zu fragen und ich werde versuchen, zu helfen: Du findest mich unter @twostraws.

Force-Unwrapping von Optionals

Swift lässt Dich seine Sicherheit umgehen, in dem Du das Ausrufezeichen benutzt: !. Wenn Du weißt, dass ein Optional definitiv einen Wert besitzt, kannst Du ihn explizit auspacken (man sagt auch "force-unwrappen"), indem Du ein Ausrufezeichen dahinter schreibst.

Bitte sei trotzdem vorsichtig: Wenn Du dies bei einer Variable probierst, die keinen Wert besitzt, wird Dein Code abstürzen.

Um ein funktionierendes Beispiel zu konstruieren, ist hier mal ein bisschen grundlegender Code:

func yearAlbumReleased(name: String) -> Int? {
    if name == "Taylor Swift" { return 2006 }
    if name == "Fearless" { return 2008 }
    if name == "Speak Now" { return 2010 }
    if name == "Red" { return 2012 }
    if name == "1989" { return 2014 }

    return nil
}

var year = yearAlbumReleased(name: "Red")

if year == nil {
    print("Ein Fehler ist aufgetreten")
} else {
    print("Es ist im Jahre \(year) erschienen")
}

Dies liefert das Jahr, in dem ein Album erschienen ist. Falls das Album nicht gefunden werden konnte, wird year auf nil gesetzt und eine Fehlermeldung ausgegeben. Andernfalls wir das Jahr ausgegeben.

Ist das wirklich so? yearAlbumReleased() gibt ein Optional Integer zurück und dieser Code benutzt kein if let um diesen Optional zu unwrappen. Im Ergebis wird das folgende ausgegeben: "Es ist im Jahre Optional(2012) erschienen" - möglicherweise nicht das, was wir wollten!

An diesem Punkt des Codes wissen wir, dass wir einen validen Wert haben, also ist es ein bisschen sinnlos, ein zweites if let zu benutzen, um den Optional sicher zu unwrappen. Daher bietet Swift eine Lösung - ändere den zweiten print()-Aufruf auf folgende Weise:

print("Es ist im Jahre \(year!) erschienen")

Beachte das Ausrufezeichen: Es bedeutet "Ich bin mir sicher, dass hier ein Wert vorliegt, also force-unwrappe ihn."

Implicitly Unwrapped Optionals

Du kannst das Aufrufezeichen auch benutzen, um implicitly unwrapped Optionals zu erstellen, welche einige Leute so richtig verwirren. Also, lies dies bitte sorgfältig!

  • Eine normale Variable muss einen Wert enthalten. Beispiel: String muss einen String enthalten, auch, wenn dieser leer ist, z.B. "". Er kann nicht nil sein.
  • Eine optional Variable kann einen Wert enthalten oder nicht. Sie muss unwrapped werden, bevor sie verwendet wird. Beispiel: String? kann einen String enthalten oder nil. Die einzige Möglichkeit, das herauszufinden, ist Unwrapping.
  • Ein implicitly unwrapped Optional kann einen Wert enthalten oder nicht. Er muss aber nicht unwrapped werden, bevor er verwendet wird. Swift wird das nicht für Dich überprüfen, also musst Du besonders vorsichtig sein. Beispiel: String! kann einen String enthalten, kann aber auch nil enthalten - und es ist Deine eigene Verantwortung, ihn angemessen zu benutzen. Er ist wie ein normales Optional, aber Swift lässt Dich den Wert benutzen, ohne die Sicherheit von Unwrapping zu verwenden. Wenn Du das tust, bedeutet das, dass Du weißt, dass da ein Wert ist - falls Du Dich aber irrst, wird Deine App abstürzen.

Die meiste Zeit mit implicitly unwrapped Optionals wirst Du verbringen, wenn Du mit User-Interface-Elementen von UIKit auf iOS oder AppKit auf macOS arbeitest. Diese müssen im Vorhinein deklariert werden, aber Du kannst sie nicht benutzen, bis sie erstellt wurden - und Apple mag es, User-Interface-Elemente im letztmöglichen Moment zu erstellen, um unnötige Arbeit zu vermeiden. Werte immer wieder unwrappen zu müssen, obwohl man genau weiß, dass sie da sind, ist eher nervig, also sind sie implicitly unwrapped.

Mache Dir keine Sorgen, falls Du implicitly unwrapped Optionals ein bisschen schwer begreiflich findest - sie werden klarer, je mehr Du mit der Sprache arbeitest.

Hacking with Swift is sponsored by Proxyman.

SPONSORED Debug 10x faster with Proxyman. Your ultimate tool to capture HTTPs requests/ responses, natively built for iPhone and macOS. You’d be surprised how much you can learn about any system by watching what it does over the network.

Try Now!

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.