Was ist ein "unverpackter Wert" in Swift?

155

Ich lerne Swift für iOS 8 / OSX 10.10, indem ich diesem Tutorial folge , und der Begriff " unverpackter Wert " wird mehrmals verwendet, wie in diesem Absatz (unter Objekte und Klasse ):

Wenn Sie mit optionalen Werten arbeiten, können Sie schreiben? vor Operationen wie Methoden, Eigenschaften und Subskription. Ist der Wert vor dem? ist null, alles nach dem? wird ignoriert und der Wert des gesamten Ausdrucks ist null. Andernfalls wird der optionale Wert ausgepackt und alles nach dem? wirkt auf den unverpackten Wert . In beiden Fällen ist der Wert des gesamten Ausdrucks ein optionaler Wert.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare?.sideLength

Ich verstehe es nicht und habe ohne Glück im Internet gesucht.

Was bedeutet das?


Bearbeiten

Nach Cezarys Antwort gibt es einen kleinen Unterschied zwischen der Ausgabe des Originalcodes und der endgültigen Lösung (auf dem Spielplatz getestet):

Originalcode

Originalcode

Cezarys Lösung

Cezarys Lösung

Die Eigenschaften der Oberklasse werden im zweiten Fall in der Ausgabe angezeigt, während im ersten Fall ein leeres Objekt vorhanden ist.

Soll das Ergebnis nicht in beiden Fällen identisch sein?

Verwandte Fragen und Antworten: Was ist ein optionaler Wert in Swift?

Bigood
quelle
1
Cezarys Antwort ist genau richtig. Außerdem gibt es ein großartiges Intro-Buch zu Swift, das KOSTENLOS auf iBooks erhältlich ist
Daniel Storm
@ DanielStormApps Dieses iBook ist eine tragbare Version des verlinkten Tutorials in meiner Frage :)
Bigood

Antworten:

289

Zunächst müssen Sie verstehen, was ein optionaler Typ ist. Ein optionaler Typ bedeutet grundsätzlich, dass die Variable sein kann nil .

Beispiel:

var canBeNil : Int? = 4
canBeNil = nil

Das Fragezeichen zeigt an, dass canBeNildies möglich istnil .

Das würde nicht funktionieren:

var cantBeNil : Int = 4
cantBeNil = nil // can't do this

Um den Wert aus Ihrer Variablen zu erhalten, falls dieser optional ist, müssen Sie ihn auspacken . Dies bedeutet nur, am Ende ein Ausrufezeichen zu setzen.

var canBeNil : Int? = 4
println(canBeNil!)

Ihr Code sollte folgendermaßen aussehen:

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare!.sideLength

Eine Nebenbemerkung:

Sie können auch Optionen zum automatischen Auspacken deklarieren, indem Sie ein Ausrufezeichen anstelle eines Fragezeichens verwenden.

Beispiel:

var canBeNil : Int! = 4
print(canBeNil) // no unwrapping needed

Eine alternative Möglichkeit, Ihren Code zu reparieren, ist:

let optionalSquare: Square! = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare.sideLength

BEARBEITEN:

Der Unterschied, den Sie sehen, ist genau das Symptom dafür, dass der optionale Wert umbrochen wird . Es gibt eine weitere Schicht darüber. Die entpackte Version zeigt nur das gerade Objekt, weil es gut entpackt ist.

Ein schneller Spielplatzvergleich:

Spielplatz

Im ersten und zweiten Fall wird das Objekt nicht automatisch entpackt, sodass Sie zwei "Ebenen" ( {{...}}) sehen, während Sie im dritten Fall nur eine Ebene sehen ({...} ) angezeigt wird, da das Objekt automatisch entpackt wird.

Der Unterschied zwischen dem ersten Fall und den beiden zweiten Fällen besteht darin, dass in den zweiten beiden Fällen ein Laufzeitfehler angezeigt wird, wenn "gesetzt" optionalSquareist nil. Mit der Syntax im ersten Fall können Sie Folgendes tun:

if let sideLength = optionalSquare?.sideLength {
    println("sideLength is not nil")
} else {
    println("sidelength is nil")
}
Cezary Wojcik
quelle
1
Danke für die klare Erklärung! Der in meiner Frage enthaltene Code stammt aus Apples Tutorial (Link in der Frage), und ich denke, er soll so funktionieren, wie er ist (und das tut er auch). Ihre endgültige und die ursprüngliche Lösung geben jedoch unterschiedliche Ergebnisse aus (siehe meine Bearbeitung). Jeder Gedanke?
Bigood
2
Cool. Tolle Erklärung. Ich bin trotzdem verwirrt. Warum sollte ich einen optionalen Wert verwenden wollen? Wann ist es sinnvoll? Warum hat Apple dieses Verhalten und diese Syntax erstellt?
Todd Lehman
1
Dies ist besonders relevant, wenn es um Klasseneigenschaften geht. Swift erfordert, dass alle nicht optionalen Methoden in der init()Funktion der Klasse initialisiert werden . Ich empfehle, die Kapitel "Die Grundlagen" (behandelt Optionen), "Initialisierung" und "Optionale Verkettung" im von Apple veröffentlichten Swift-Buch zu lesen.
Cezary Wojcik
2
@ToddLehman Apple hat dies erstellt, damit Sie viel sichereren Code schreiben können. Stellen Sie sich zum Beispiel alle Nullzeiger-Ausnahmen in Java vor, die ein Programm zum Absturz bringen, weil jemand vergessen hat, nach Null zu suchen. Oder seltsames Verhalten bei Objective-C, weil Sie vergessen haben, nach Null zu suchen. Mit dem optionalen Typ können Sie nicht vergessen, mit Null umzugehen. Der Compiler zwingt Sie, damit umzugehen.
Erik Engheim
5
@AdamSmith - Aus einem Java-Hintergrund kommend, kann ich definitiv die Verwendung der Optionen sehen ... aber auch (in jüngerer Zeit) aus einem Objective-C-Hintergrund habe ich immer noch Probleme, die Verwendung zu sehen, weil Objective- Mit C können Sie explizit Nachrichten an keine Objekte senden. Hmm. Ich würde gerne sehen, wie jemand ein Beispiel dafür schreibt, wie er dazu beiträgt, Code kürzer und sicherer zu machen als den entsprechenden Code, der sie nicht verwendet. Ich sage nicht, dass sie nicht nützlich sind; Ich sage nur, ich verstehe es noch nicht.
Todd Lehman
17

Die vorhandene richtige Antwort ist großartig, aber ich fand, dass ich eine gute Analogie brauchte, um dies vollständig zu verstehen, da dies ein sehr abstraktes und seltsames Konzept ist.

Lassen Sie mich diesen "rechtshirnigen" Entwicklern (visuelles Denken) helfen, indem ich zusätzlich zur richtigen Antwort eine andere Perspektive gebe. Hier ist eine gute Analogie, die mir sehr geholfen hat.

Geburtstagsgeschenk-Verpackungsanalogie

Stellen Sie sich Optionals wie Geburtstagsgeschenke vor, die in einer steifen, harten, farbigen Verpackung geliefert werden.

Sie wissen nicht, ob sich etwas in der Verpackung befindet, bis Sie das Geschenk auspacken - vielleicht ist überhaupt nichts darin! Wenn sich etwas darin befindet, könnte es sich um ein weiteres Geschenk handeln, das ebenfalls verpackt ist und möglicherweise auch nichts enthält . Sie könnten sogar 100 verschachtelte Geschenke auspacken, um schließlich festzustellen, dass es nichts anderes als das Verpacken gab .

Wenn der Wert des optionalen nicht ist nil, haben Sie jetzt ein Feld mit etwas angezeigt . Insbesondere wenn der Wert nicht explizit eingegeben wird und eine Variable und keine vordefinierte Konstante ist, müssen Sie die Box möglicherweise noch öffnen, bevor Sie etwas Spezifisches darüber wissen können, was sich in der Box befindet, wie welcher Typ es ist oder welcher ist Wert ist.

Was ist in der Box?! Analogie

Selbst nachdem Sie die Variable ausgepackt haben, sind Sie immer noch wie Brad Pitt in der letzten Szene in SE7EN ( Warnung : Spoiler und sehr R-bewertete Schimpfwörter und Gewalt), denn selbst nachdem Sie die Gegenwart ausgepackt haben, befinden Sie sich in der folgenden Situation: Sie haben jetzt niloder eine Box mit etwas (aber Sie wissen nicht was).

Sie kennen vielleicht die Art des Etwas . Wenn Sie beispielsweise die Variable als Typ deklariert [Int:Any?]haben, wissen Sie, dass Sie ein (möglicherweise leeres) Wörterbuch mit ganzzahligen Indizes haben, das umschlossene Inhalte eines alten Typs liefert.

Aus diesem Grund kann der Umgang mit Sammlungstypen (Wörterbücher und Arrays) in Swift etwas haarig werden.

Ein typisches Beispiel:

typealias presentType = [Int:Any?]

func wrap(i:Int, gift:Any?) -> presentType? {
    if(i != 0) {
        let box : presentType = [i:wrap(i-1,gift:gift)]
        return box
    }
    else {
        let box = [i:gift]
        return box
    }
}

func getGift() -> String? {
    return "foobar"
}

let f00 = wrap(10,gift:getGift())

//Now we have to unwrap f00, unwrap its entry, then force cast it into the type we hope it is, and then repeat this in nested fashion until we get to the final value.

var b4r = (((((((((((f00![10]! as! [Int:Any?])[9]! as! [Int:Any?])[8]! as! [Int:Any?])[7]! as! [Int:Any?])[6]! as! [Int:Any?])[5]! as! [Int:Any?])[4]! as! [Int:Any?])[3]! as! [Int:Any?])[2]! as! [Int:Any?])[1]! as! [Int:Any?])[0])

//Now we have to DOUBLE UNWRAP the final value (because, remember, getGift returns an optional) AND force cast it to the type we hope it is

let asdf : String = b4r!! as! String

print(asdf)
CommaToast
quelle
schön 😊 😊😊 😊 😊😊 😊 Sam
SamSol
liebe die Analogie! Gibt es eine bessere Möglichkeit, die Kartons auszupacken? in Ihrem Codebeispiel
David T.
Schrödingers Katze ist in der Box
Jacksonkr
Danke, dass du mich verwirrt hast ... Was für ein Programmierer gibt ein Geburtstagsgeschenk, das sowieso leer ist? irgendwie gemein
delta2flat
@ Jacksonkr Oder nicht.
Amsmath
13

Swift legt großen Wert auf Typensicherheit. Die gesamte Swift-Sprache wurde unter Berücksichtigung der Sicherheit entwickelt. Es ist eines der Markenzeichen von Swift und eines, das Sie mit offenen Armen begrüßen sollten. Es hilft bei der Entwicklung von sauberem, lesbarem Code und verhindert, dass Ihre Anwendung abstürzt.

Alle Optionen in Swift sind mit dem ?Symbol gekennzeichnet. Durch die Einstellung des ?in nach dem Namen des Typs , die Sie erklärt werden als optional Sie sind im Wesentlichen Gießen dies nicht als Typ , in dem vor dem ?, sondern als optionalem Typ.


Hinweis: Eine Variable oder ein Typ Intist nicht identisch mit Int?. Es handelt sich um zwei verschiedene Typen, die nicht miteinander betrieben werden können.


Optional verwenden

var myString: String?

myString = "foobar"

Dies bedeutet nicht , dass Sie mit einer Art von arbeiten String. Dies bedeutet, dass Sie mit einem Typ von String?(String Optional oder Optional String) arbeiten. In der Tat, wann immer Sie versuchen

print(myString)

Zur Laufzeit wird die Debug-Konsole gedruckt Optional("foobar"). Der Optional()Teil " " gibt an, dass diese Variable zur Laufzeit einen Wert haben kann oder nicht, enthält aber gerade die Zeichenfolge "foobar". Diese " Optional()" -Anzeige bleibt bestehen, es sei denn, Sie führen den so genannten "Auspacken" des optionalen Werts durch.

Das Auspacken eines optionalen Elements bedeutet, dass Sie diesen Typ jetzt als nicht optional umwandeln . Dadurch wird ein neuer Typ generiert und der Wert, der sich in diesem optionalen Typ befand, dem neuen nicht optionalen Typ zugewiesen. Auf diese Weise können Sie Operationen für diese Variable ausführen, da vom Compiler ein fester Wert garantiert wurde.


Das bedingte Auspacken prüft, ob der Wert in der Option optional ist niloder nicht. Ist dies nicht der nilFall, gibt es eine neu erstellte Konstantenvariable, der der Wert zugewiesen und in die nicht optionale Konstante entpackt wird . Und von dort aus können Sie das nicht optionale im ifBlock sicher verwenden .

Hinweis: Sie können Ihrer bedingt entpackten Konstante denselben Namen geben wie der optionalen Variablen, die Sie entpacken.

if let myString = myString {
    print(myString)
    // will print "foobar"
}

Das bedingte Auspacken von Optionen ist die sauberste Möglichkeit, auf den Wert eines optionalen Elements zuzugreifen. Wenn dieser einen Nullwert enthält, wird alles im if let-Block nicht ausgeführt. Natürlich können Sie wie jede if-Anweisung einen else-Block einfügen

if let myString = myString {
    print(myString)
    // will print "foobar"
}
else {
    print("No value")
}

Das gewaltsame Auspacken erfolgt durch Verwendung des sogenannten !("Knall") - Operators. Dies ist weniger sicher, ermöglicht jedoch das Kompilieren Ihres Codes. Wenn Sie jedoch den Bang-Operator verwenden, müssen Sie zu 1000% sicher sein, dass Ihre Variable tatsächlich einen festen Wert enthält, bevor Sie sie zwangsweise auspacken.

var myString: String?

myString = "foobar"

print(myString!)

Dies ist ein vollständig gültiger Swift-Code. Es druckt den Wert aus myString, der als "foobar" eingestellt wurde. Der Benutzer würde foobarin der Konsole gedruckt sehen und das war's auch schon. Nehmen wir jedoch an, der Wert wurde nie festgelegt:

var myString: String?

print(myString!) 

Jetzt haben wir eine andere Situation in den Händen. Im Gegensatz zu Objective-C stürzt der Versuch ab, eine Option zwangsweise zu entpacken, und die Option wurde nicht festgelegt nil. Wenn Sie versuchen, die Option zu entpacken, um zu sehen, was in Ihrer Anwendung enthalten ist, stürzt sie ab.


Auspacken mit Typ Casting . Wie bereits erwähnt, können Sie, obwohl Sie unwrappingoptional sind, tatsächlich in einen nicht optionalen Typ umwandeln, das nicht optionale jedoch auch in einen anderen Typ umwandeln. Beispielsweise:

var something: Any?

Irgendwo in unserem Code wird die Variable somethingmit einem Wert gesetzt. Vielleicht verwenden wir Generika oder es gibt eine andere Logik, die dazu führt, dass sich dies ändert. Später in unserem Code möchten wir ihn verwenden, können ihn somethingaber dennoch anders behandeln, wenn es sich um einen anderen Typ handelt. In diesem Fall möchten Sie das asSchlüsselwort verwenden, um dies zu bestimmen:

Hinweis: Mit dem asOperator geben Sie cast in Swift ein.

// Conditionally
if let thing = something as? Int {
    print(thing) // 0
}

// Optionally
let thing = something as? Int
print(thing) // Optional(0)

// Forcibly
let thing = something as! Int
print(thing) // 0, if no exception is raised

Beachten Sie den Unterschied zwischen den beiden asSchlüsselwörtern. Wie zuvor, als wir eine Option gewaltsam ausgepackt haben, haben wir dazu den !Bang-Operator verwendet. Hier werden Sie das Gleiche tun, aber anstatt nur als nicht optional zu gießen, gießen Sie es auch als Int. Und es muss in der Lage sein, niedergeschlagen zu werden, da Intsonst die Verwendung des Bang-Operators, wenn der Wert nilIhre Anwendung ist, abstürzt.

Und um diese Variablen überhaupt in irgendeiner Art oder mathematischen Operation zu verwenden, müssen sie dazu ausgepackt werden.

Beispielsweise dürfen in Swift nur gültige Nummerndatentypen derselben Art aufeinander angewendet werden. Wenn Sie einen Typ mit dem as!umwandeln, erzwingen Sie den Downcast dieser Variablen, als ob Sie sicher sind , dass es sich um diesen Typ handelt. Daher können Sie sicher arbeiten und Ihre Anwendung nicht zum Absturz bringen. Dies ist in Ordnung, solange die Variable tatsächlich von dem Typ ist, auf den Sie sie übertragen, da Sie sonst ein Durcheinander in Ihren Händen haben.

Durch das Casting mit as!kann Ihr Code jedoch kompiliert werden. Beim Casting mit einem as?ist eine andere Geschichte. In der Tat as?erklärt Sie Intals völlig anderen Datentyp alle zusammen.

Jetzt ist es Optional(0)

Und wenn Sie jemals versucht haben, Ihre Hausaufgaben zu machen, schreiben Sie so etwas wie

1 + Optional(1) = 2

Ihr Mathematiklehrer hätte Ihnen wahrscheinlich ein "F" gegeben. Gleiches gilt für Swift. Nur dass Swift lieber gar nicht kompilieren möchte, als Ihnen eine Note zu geben. Denn am Ende des Tages kann die Option tatsächlich Null sein .

Sicherheit geht vor Kinder.

Brandon A.
quelle
Schöne Erklärung der optionalen Typen. Hat mir geholfen, die Dinge zu klären.
Tobe_Sta
0

'?' bedeutet optionaler Verkettungsausdruck,

das '!' bedeutet Kraftwert
Geben Sie hier die Bildbeschreibung ein

gkgy
quelle