Was bedeutet "Schwerwiegender Fehler: Beim Auspacken eines optionalen Werts unerwartet Null gefunden"?


415

Mein Swift-Programm stürzt mit EXC_BAD_INSTRUCTIONeinem der folgenden ähnlichen Fehler ab. Was bedeutet dieser Fehler und wie behebe ich ihn?

Schwerwiegender Fehler: Beim Auspacken eines optionalen Werts wurde unerwartet Null gefunden

oder

Schwerwiegender Fehler: Unerwartet wurde Null gefunden, während implizit ein optionaler Wert entpackt wurde


Dieser Beitrag soll Antworten auf "unerwartet gefundene Null" -Probleme sammeln, damit sie nicht verstreut und schwer zu finden sind. Fühlen Sie sich frei, Ihre eigene Antwort hinzuzufügen oder die vorhandene Wiki-Antwort zu bearbeiten .


8
@RobertColumbia, dies ist ein Community-Wiki-Q & A-Paar mit einer umfassenden Dokumentation des Problems. Ich denke nicht, dass diese Frage wegen fehlender MCVE geschlossen werden sollte.
JAL

Variable wie diese erstellen [var nameOfDaughter: String? ] und verwenden Sie diese Variable wie folgt [wenn _ = nameOfDaughter {}] der beste Ansatz ist.
iOS

Antworten:


673

Diese Antwort ist Community-Wiki . Wenn Sie der Meinung sind, dass es besser gemacht werden könnte, können Sie es jederzeit bearbeiten !

Hintergrund: Was ist optional?

In Swift Optionalist dies ein generischer Typ , der einen Wert (jeglicher Art) oder überhaupt keinen Wert enthalten kann.

In vielen anderen Programmiersprachen wird häufig ein bestimmter "Sentinel" -Wert verwendet, um auf das Fehlen eines Werts hinzuweisen . In Objective-C zeigt beispielsweise nil(der Nullzeiger ) das Fehlen eines Objekts an. Bei der Arbeit mit primitiven Typen wird dies jedoch schwieriger. Sollte -1dies verwendet werden, um das Fehlen einer Ganzzahl oder vielleicht INT_MINeiner anderen Ganzzahl anzuzeigen ? Wenn ein bestimmter Wert als "keine Ganzzahl" ausgewählt wird, bedeutet dies, dass er nicht mehr als gültiger Wert behandelt werden kann.

Swift ist eine typsichere Sprache, dh die Sprache hilft Ihnen dabei, klar zu machen, mit welchen Arten von Werten Ihr Code arbeiten kann. Wenn ein Teil Ihres Codes einen String erwartet, verhindert die Typensicherheit, dass Sie ihm versehentlich einen Int übergeben.

In Swift kann jeder Typ optional gemacht werden . Ein optionaler Wert kann einen beliebigen Wert des ursprünglichen Typs oder des speziellen Werts annehmen nil.

Optionale Optionen werden mit einem ?Suffix für den Typ definiert:

var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int?    // `nil` is the default when no value is provided

Das Fehlen eines Wertes in einem optionalen wird angezeigt durch nil:

anOptionalInt = nil

(Beachten Sie, dass dies nilnicht mit dem nilin Objective-C identisch ist . In Objective-C nilfehlt ein gültiger Objektzeiger . In Swift sind Optionals nicht auf Objekte / Referenztypen beschränkt. Optional verhält es sich ähnlich wie Haskell's Maybe .)


Warum wurde " schwerwiegender Fehler: Beim Auspacken eines optionalen Werts unerwartet Null gefunden" angezeigt ?

Um auf den Wert eines optionalen Elements zuzugreifen (falls überhaupt vorhanden), müssen Sie ihn auspacken . Ein optionaler Wert kann sicher oder gewaltsam ausgepackt werden. Wenn Sie eine Option zwangsweise auspacken und sie keinen Wert hat, stürzt Ihr Programm mit der obigen Meldung ab.

Xcode zeigt Ihnen den Absturz an, indem Sie eine Codezeile markieren. Das Problem tritt in dieser Zeile auf.

abgestürzte Linie

Dieser Absturz kann mit zwei verschiedenen Arten des Force-Unwraps auftreten:

1. Explizite Force-Entpackung

Dies erfolgt mit dem !Bediener optional. Zum Beispiel:

let anOptionalString: String?
print(anOptionalString!) // <- CRASH

Schwerwiegender Fehler: Beim Auspacken eines optionalen Werts wurde unerwartet Null gefunden

Wie anOptionalStringist nilhier, Sie werden einen Absturz auf der Linie, wo Sie unwrap es erzwingen.

2. Implizit ausgepackte Optionen

Diese werden !eher mit einem als mit einem ?nach dem Typ definiert.

var optionalDouble: Double!   // this value is implicitly unwrapped wherever it's used

Es wird angenommen, dass diese Optionen einen Wert enthalten. Wenn Sie daher auf eine implizit entpackte Option zugreifen, wird diese automatisch für Sie erzwungen. Wenn es keinen Wert enthält, stürzt es ab.

print(optionalDouble) // <- CRASH

Schwerwiegender Fehler: Unerwartet wurde Null gefunden, während implizit ein optionaler Wert entpackt wurde

Um herauszufinden, welche Variable den Absturz verursacht hat, können Sie gedrückt halten, während Sie auf klicken, um die Definition anzuzeigen, in der Sie möglicherweise den optionalen Typ finden.

Insbesondere IBOutlets sind normalerweise implizit ausgepackte Optionen. Dies liegt daran, dass Ihr xib oder Storyboard die Outlets nach der Initialisierung zur Laufzeit verbindet . Sie sollten daher sicherstellen, dass Sie nicht auf Outlets zugreifen, bevor diese geladen werden. Sie sollten auch überprüfen, ob die Verbindungen in Ihrer Storyboard- / XIB-Datei korrekt sind. Andernfalls werden die Werte nilzur Laufzeit angezeigt und stürzen daher ab, wenn sie implizit entpackt werden . Versuchen Sie beim Reparieren von Verbindungen, die Codezeilen zu löschen, die Ihre Steckdosen definieren, und verbinden Sie sie dann erneut.


Wann sollte ich jemals ein optionales Auspacken erzwingen?

Explizite Force-Entpackung

In der Regel sollten Sie niemals explizit das Entpacken einer Option mit dem !Operator erzwingen . Es kann Fälle geben, in denen die Verwendung !akzeptabel ist. Sie sollten sie jedoch nur verwenden, wenn Sie zu 100% sicher sind, dass die Option einen Wert enthält.

Es kann zwar vorkommen, dass Sie das Entpacken mit Gewalt anwenden können, da Sie wissen , dass ein optionales Element einen Wert enthält - es gibt jedoch keinen einzigen Element Ort, an dem Sie dieses optionale Element nicht sicher auspacken können.

Implizit ausgepackte Optionen

Diese Variablen sind so konzipiert, dass Sie ihre Zuordnung bis zu einem späteren Zeitpunkt in Ihrem Code verschieben können. Es liegt in Ihrer Verantwortung, sicherzustellen, dass sie einen Wert haben, bevor Sie darauf zugreifen. Da es sich jedoch um ein gewaltsames Auspacken handelt, sind sie von Natur aus immer noch unsicher, da davon ausgegangen wird, dass Ihr Wert nicht Null ist, obwohl die Zuweisung von Null gültig ist.

Sie sollten nur implizit entpackte Optionen als letzten Ausweg verwenden . Wenn Sie eine verzögerte Variable verwenden oder einen Standardwert für eine Variable angeben können, sollten Sie dies tun, anstatt eine implizit entpackte Option zu verwenden.

Es gibt jedoch einige Szenarien, in denen implizit ausgepackte Optionen von Vorteil sind , und Sie können weiterhin verschiedene Methoden zum sicheren Auspacken verwenden, wie unten aufgeführt. Sie sollten sie jedoch immer mit der gebotenen Vorsicht verwenden.


Wie kann ich sicher mit Optionals umgehen?

Der einfachste Weg, um zu überprüfen, ob ein optionales Element einen Wert enthält, besteht darin, ihn mit zu vergleichen nil.

if anOptionalInt != nil {
    print("Contains a value!")
} else {
    print("Doesn’t contain a value.")
}

In 99,9% der Fälle, in denen Sie mit Optionen arbeiten, möchten Sie tatsächlich auf den darin enthaltenen Wert zugreifen, sofern dieser überhaupt einen enthält. Dazu können Sie die optionale Bindung verwenden .

Optionale Bindung

Mit der optionalen Bindung können Sie überprüfen, ob eine Option einen Wert enthält - und den nicht umschlossenen Wert einer neuen Variablen oder Konstante zuweisen. Es verwendet die Syntax if let x = anOptional {...}oderif var x = anOptional {...} , je nachdem, ob Sie den Wert der neuen Variablen nach dem Binden ändern müssen.

Zum Beispiel:

if let number = anOptionalInt {
    print("Contains a value! It is \(number)!")
} else {
    print("Doesn’t contain a number")
}

Überprüfen Sie zunächst, ob das optionale Element einen Wert enthält. Wenn dies der Fall ist , wird der Wert "entpackt" einer neuen Variablen ( number) zugewiesen, die Sie dann frei verwenden können, als wäre sie nicht optional. Wenn das optionale Element keinen Wert enthält, wird die else-Klausel wie erwartet aufgerufen.

Das Schöne an der optionalen Bindung ist, dass Sie mehrere Optionen gleichzeitig auspacken können. Sie können die Anweisungen einfach durch ein Komma trennen. Die Anweisung ist erfolgreich, wenn alle Optionen ausgepackt wurden.

var anOptionalInt : Int?
var anOptionalString : String?

if let number = anOptionalInt, let text = anOptionalString {
    print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
    print("One or more of the optionals don’t contain a value")
}

Ein weiterer guter Trick ist, dass Sie auch Kommas verwenden können, um nach dem Auspacken nach einer bestimmten Bedingung für den Wert zu suchen.

if let number = anOptionalInt, number > 0 {
    print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}

Der einzige Haken bei der Verwendung der optionalen Bindung in einer if-Anweisung besteht darin, dass Sie nur innerhalb des Bereichs der Anweisung auf den nicht umschlossenen Wert zugreifen können. Wenn Sie außerhalb des Gültigkeitsbereichs der Anweisung Zugriff auf den Wert benötigen, können Sie eine Guard-Anweisung verwenden .

Mit einer Guard-Anweisung können Sie eine Bedingung für den Erfolg definieren. Der aktuelle Bereich wird nur dann weiter ausgeführt, wenn diese Bedingung erfüllt ist. Sie werden mit der Syntax definiert guard condition else {...}.

Um sie mit einer optionalen Bindung zu verwenden, können Sie Folgendes tun:

guard let number = anOptionalInt else {
    return
}

(Beachten Sie, dass innerhalb des Schutzkörpers, Sie müssen eine der Verwendung Steuertransferanweisungen , um den Umfang des aktuell ausgeführten Code zu verlassen).

Wenn es anOptionalInteinen Wert enthält, wird dieser entpackt und der neuen numberKonstante zugewiesen . Der Code nach dem Guard wird dann weiter ausgeführt. Wenn es keinen Wert enthält, führt der Guard den Code in den Klammern aus, was zur Übertragung der Kontrolle führt, sodass der Code unmittelbar danach nicht ausgeführt wird.

Das Schöne an Guard-Anweisungen ist, dass der nicht umschlossene Wert jetzt in Code verwendet werden kann, der auf die Anweisung folgt (da wir wissen, dass zukünftiger Code nur ausgeführt werden kann , wenn das optionale einen Wert hat). Dies ist ideal, um "Pyramiden des Untergangs" zu beseitigen. die durch das Verschachteln mehrerer if-Anweisungen entstehen.

Zum Beispiel:

guard let number = anOptionalInt else {
    return
}

print("anOptionalInt contains a value, and it’s: \(number)!")

Guards unterstützen auch dieselben netten Tricks wie die if-Anweisung, z. B. das gleichzeitige Auspacken mehrerer Optionen und die Verwendung der whereKlausel.

Egal , ob Sie eine if oder Schutzerklärung verwenden , hängt vollständig ab , ob ein künftiges Code erfordert die optionalen Wert enthalten.

Null-Koaleszenz-Operator

Der Nil Coalescing Operator ist eine raffinierte Kurzversion des ternären bedingten Operators , der hauptsächlich zum Konvertieren von Optionen in Nicht-Optionen entwickelt wurde. Es hat die Syntax a ?? b, wobei aes sich um einen optionalen Typ handelt und bderselbe Typ ist wiea (obwohl normalerweise nicht optional).

Sie können im Wesentlichen sagen: „Wenn aein Wert enthalten ist, packen Sie ihn aus. Wenn dies nicht der Fall ist, kehren Sie bstattdessen zurück. “ Zum Beispiel könnten Sie es so verwenden:

let number = anOptionalInt ?? 0

Dadurch wird eine numberKonstante vom IntTyp definiert, die entweder den Wert von anOptionalIntenthält, wenn sie einen Wert enthält, oder auf 0andere Weise.

Es ist nur eine Abkürzung für:

let number = anOptionalInt != nil ? anOptionalInt! : 0

Optionale Verkettung

Sie können die optionale Verkettung verwenden, um eine Methode aufzurufen oder auf eine optionale Eigenschaft zuzugreifen. Dies erfolgt einfach durch Suffixieren des Variablennamens mit a? bei Verwendung .

Angenommen, wir haben eine Variable foovom Typ einer optionalen FooInstanz.

var foo : Foo?

Wenn wir eine Methode aufrufen wollten foo, die nichts zurückgibt, können wir einfach Folgendes tun:

foo?.doSomethingInteresting()

Wenn fooein Wert enthalten ist, wird diese Methode darauf aufgerufen. Wenn dies nicht der Fall ist, passiert nichts Schlimmes - der Code wird einfach weiter ausgeführt.

(Dies ähnelt dem Senden von Nachrichten an nilObjective-C.)

Dies kann daher auch zum Festlegen von Eigenschaften sowie von Aufrufmethoden verwendet werden. Zum Beispiel:

foo?.bar = Bar()

Auch hier wird nichts Schlimmes passieren, wenn es so fooist nil. Ihr Code wird einfach weiter ausgeführt.

Ein weiterer nützlicher Trick, den Sie mit der optionalen Verkettung ausführen können, besteht darin, zu überprüfen, ob das Festlegen einer Eigenschaft oder das Aufrufen einer Methode erfolgreich war. Sie können dies tun, indem Sie den Rückgabewert mit vergleichen nil.

(Dies liegt daran, dass ein optionaler Wert zurückgegeben wird Void?und nicht Voidfür eine Methode, die nichts zurückgibt.)

Zum Beispiel:

if (foo?.bar = Bar()) != nil {
    print("bar was set successfully")
} else {
    print("bar wasn’t set successfully")
}

Beim Versuch, auf Eigenschaften zuzugreifen oder Methoden aufzurufen, die einen Wert zurückgeben, wird es jedoch etwas schwieriger. Da dies foooptional ist, ist alles, was von ihm zurückgegeben wird, ebenfalls optional. Um dies zu beheben, können Sie entweder die zurückgegebenen Optionen mit einer der oben genannten Methoden auspacken oder sich fooselbst auspacken , bevor Sie auf Methoden zugreifen oder Methoden aufrufen, die Werte zurückgeben.

Wie der Name schon sagt, können Sie diese Anweisungen auch miteinander verketten. Dies bedeutet, dass Sie, wenn Sie fooeine optionale Eigenschaft haben baz, die eine Eigenschaft quxhat, Folgendes schreiben können:

let optionalQux = foo?.baz?.qux

Da foound bazoptional sind, ist der von zurückgegebene Wert quximmer optional, unabhängig davon, ob er quxselbst optional ist.

map und flatMap

Eine häufig nicht ausreichend genutzte Funktion mit Optionen ist die Möglichkeit, die Funktionen mapund zu flatMapverwenden. Mit diesen können Sie nicht optionale Transformationen auf optionale Variablen anwenden. Wenn ein optionales Element einen Wert hat, können Sie eine bestimmte Transformation darauf anwenden. Wenn es keinen Wert hat, bleibt es nil.

Angenommen, Sie haben eine optionale Zeichenfolge:

let anOptionalString:String?

Durch Anwenden der mapFunktion darauf können wir die stringByAppendingStringFunktion verwenden, um sie mit einer anderen Zeichenfolge zu verketten.

Da stringByAppendingStringein nicht optionales Zeichenfolgenargument verwendet wird, können wir unsere optionale Zeichenfolge nicht direkt eingeben. Mit using mapkönnen wir jedoch allow stringByAppendingStringverwenden, wenn anOptionalStringein Wert vorhanden ist.

Zum Beispiel:

var anOptionalString:String? = "bar"

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // Optional("foobar")

Wenn anOptionalStringjedoch kein Wert vorhanden ist, mapwird zurückgegeben nil. Zum Beispiel:

var anOptionalString:String?

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // nil

flatMapfunktioniert ähnlich wie map, außer dass Sie eine weitere Option innerhalb des Verschlusskörpers zurückgeben können. Dies bedeutet, dass Sie eine Option in einen Prozess eingeben können, für den eine nicht optionale Eingabe erforderlich ist, eine Option jedoch selbst ausgeben kann.

try!

Das Fehlerbehandlungssystem von Swift kann sicher mit Do-Try-Catch verwendet werden :

do {
    let result = try someThrowingFunc() 
} catch {
    print(error)
}

Wenn someThrowingFunc()ein Fehler ausgelöst wird, wird der Fehler sicher im catchBlock abgefangen .

Die errorKonstante, die Sie im catchBlock sehen, wurde von uns nicht deklariert - sie wird automatisch von generiert catch.

Sie können sich auch errorselbst deklarieren , es hat den Vorteil, dass Sie es in ein nützliches Format umwandeln können, zum Beispiel:

do {
    let result = try someThrowingFunc()    
} catch let error as NSError {
    print(error.debugDescription)
}

Auf trydiese Weise können Sie Fehler von Wurffunktionen versuchen, abfangen und behandeln.

Es gibt auch, try?was den Fehler absorbiert:

if let result = try? someThrowingFunc() {
    // cool
} else {
    // handle the failure, but there's no error information available
}

Das Fehlerbehandlungssystem von Swift bietet jedoch auch eine Möglichkeit, den Versuch zu erzwingen try!:

let result = try! someThrowingFunc()

Die in diesem Beitrag erläuterten Konzepte gelten auch hier: Wenn ein Fehler ausgegeben wird, stürzt die Anwendung ab.

Sie sollten es nur verwenden, try!wenn Sie nachweisen können, dass das Ergebnis in Ihrem Kontext niemals fehlschlägt - und dies ist sehr selten.

Meistens verwenden Sie das komplette Do-Try-Catch-System - und das optionale try?- in den seltenen Fällen, in denen die Behandlung des Fehlers nicht wichtig ist.


Ressourcen


87
Persönlich möchte ich Ihnen dafür danken, dass Sie sich die Mühe gemacht haben, all dies zu schreiben. Ich bin der Meinung, dass dies sowohl für Anfänger als auch für Experten mit Swift hilfreich sein wird.
Pranav Wadhwa

15
Ich bin froh, dass Sie es hilfreich fanden. Diese Antwort ist ein Community-Wiki, es ist also eine Zusammenarbeit zwischen vielen Menschen (bisher 7)!
jtbandes

1
Dieser Fehler tritt in meinem Fall nur sporadisch auf. Irgendwelche Vorschläge? Code in stackoverflow.com/questions/50933681/…
py_ios_dev

1
Vielleicht ist es an der Zeit, diese Antwort zu aktualisieren, um sie compactMap()anstelle von zu verwenden flatMap().
Nicolas Miari

Ich denke, die beste und kurzeste Lösung ist der "Nil Coalescing Operator", oder?
Mehdigriche

65

TL; DR Antwort

Mit sehr wenigen Ausnahmen ist diese Regel golden:

Vermeiden Sie die Verwendung von !

Deklarieren Sie die Variable optional ( ?), nicht implizit entpackte Optionals (IUO) ( !)

Mit anderen Worten, verwenden Sie lieber:
var nameOfDaughter: String?

Anstatt:
var nameOfDaughter: String!

Entpacken Sie die optionale Variable mit if letoderguard let

Entweder die Variable wie folgt auspacken:

if let nameOfDaughter = nameOfDaughter {
    print("My daughters name is: \(nameOfDaughter)")
}

Oder so:

guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")

Diese Antwort sollte prägnant sein, zum vollständigen Verständnis die akzeptierte Antwort lesen


Ressourcen


40

Diese Frage taucht die ganze Zeit bei SO auf. Es ist eines der ersten Dinge, mit denen neue Swift-Entwickler zu kämpfen haben.

Hintergrund:

Swift verwendet das Konzept der "Optionals", um mit Werten umzugehen, die einen Wert enthalten können oder nicht. In anderen Sprachen wie C können Sie den Wert 0 in einer Variablen speichern, um anzuzeigen, dass sie keinen Wert enthält. Was ist jedoch, wenn 0 ein gültiger Wert ist? Dann könnten Sie -1 verwenden. Was ist, wenn -1 ein gültiger Wert ist? Und so weiter.

Mit Swift-Optionen können Sie eine Variable eines beliebigen Typs so einrichten, dass sie entweder einen gültigen Wert oder keinen Wert enthält.

Sie setzen ein Fragezeichen nach dem Typ, wenn Sie eine Variable als Mittelwert deklarieren (Typ x oder kein Wert).

Ein optionales Element ist tatsächlich ein Container, der entweder eine Variable eines bestimmten Typs oder nichts enthält.

Ein optionales Element muss "ausgepackt" werden, um den darin enthaltenen Wert abzurufen.

Das "!" operator ist ein "Force Unwrap" -Operator. Es heißt "Vertrau mir. Ich weiß, was ich tue. Ich garantiere, dass die Variable bei der Ausführung dieses Codes kein Null enthält." Wenn Sie sich irren, stürzen Sie ab.

Vermeiden Sie das "!", Wenn Sie nicht wirklich wissen , was Sie tun. Bediener zum Auspacken zwingen. Es ist wahrscheinlich die größte Absturzquelle für Anfänger von Swift-Programmierern.

Umgang mit Optionen:

Es gibt viele andere Möglichkeiten, mit Optionen umzugehen, die sicherer sind. Hier sind einige (keine vollständige Liste)

Sie können "optionale Bindung" oder "if let" verwenden, um zu sagen, "wenn diese Option einen Wert enthält, speichern Sie diesen Wert in einer neuen, nicht optionalen Variablen. Wenn die Option keinen Wert enthält, überspringen Sie den Hauptteil dieser if-Anweisung ".

Hier ist ein Beispiel für eine optionale Bindung mit unserem foooptionalen:

if let newFoo = foo //If let is called optional binding. {
  print("foo is not nil")
} else {
  print("foo is nil")
}

Beachten Sie, dass die Variable, die Sie definieren, wenn Sie optionales Biding verwenden, nur im Hauptteil der if-Anweisung vorhanden ist (nur "im Gültigkeitsbereich" ist).

Alternativ können Sie eine Guard-Anweisung verwenden, mit der Sie Ihre Funktion beenden können, wenn die Variable Null ist:

func aFunc(foo: Int?) {
  guard let newFoo = input else { return }
  //For the rest of the function newFoo is a non-optional var
}

Guard-Anweisungen wurden in Swift 2 hinzugefügt. Mit Guard können Sie den "goldenen Pfad" durch Ihren Code beibehalten und immer mehr verschachtelte ifs vermeiden, die manchmal aus der Verwendung der optionalen Bindung "if let" resultieren.

Es gibt auch ein Konstrukt, das als "Null-Koaleszenz-Operator" bezeichnet wird. Es hat die Form "optional_var ?? replace_val". Es gibt eine nicht optionale Variable mit demselben Typ wie die im optionalen Daten enthaltenen Daten zurück. Wenn das optionale Element nil enthält, wird der Wert des Ausdrucks nach dem "??" Symbol.

Sie könnten also folgenden Code verwenden:

let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")

Sie können auch die Fehlerbehandlung try / catch oder guard verwenden, aber im Allgemeinen ist eine der anderen oben genannten Techniken sauberer.

BEARBEITEN:

Ein anderes, etwas subtileres Problem mit Optionen ist "implizit ausgepackte Optionen". Wenn wir foo deklarieren, können wir sagen:

var foo: String!

In diesem Fall ist foo immer noch optional, aber Sie müssen es nicht auspacken, um darauf zu verweisen. Das bedeutet, dass Sie jedes Mal, wenn Sie versuchen, auf foo zu verweisen, abstürzen, wenn es gleich Null ist.

Also dieser Code:

var foo: String!


let upperFoo = foo.capitalizedString

Wird beim Verweis auf die Eigenschaft "kapitalisiert" von foo abstürzen, obwohl wir foo nicht zwangsweise auspacken. Der Druck sieht gut aus, ist es aber nicht.

Daher möchten Sie mit implizit ausgepackten Optionen sehr vorsichtig sein. (und vielleicht sogar ganz vermeiden, bis Sie ein solides Verständnis für Optionen haben.)

Fazit: Wenn Sie Swift zum ersten Mal lernen, geben Sie das "!" Charakter ist nicht Teil der Sprache. Es wird dich wahrscheinlich in Schwierigkeiten bringen.


7
Sie sollten in Betracht ziehen, dies zu einem öffentlichen Wiki zu machen.
Vacawama

3
Bearbeiten Sie Ihre Antwort und unter dem Bearbeitungsfeld rechts befindet sich ein Kontrollkästchen für das Community-Wiki . Sie werden keinen Repräsentanten mehr für die Antwort bekommen, aber ich weiß, dass dies sowieso kein eklatanter Repräsentant ist.
Vacawama

6
Angesichts der Tatsache, dass diese Frage auf der Website millionenfach gestellt wird, würde ich hoffen, dass die Antwort weitaus vollständiger ist, wenn ich mir die Zeit nehmen würde, eine selbst beantwortete Frage als kanonische Antwort auf die Frage zu veröffentlichen. Es gibt nichts über guardKlauseln. Es gibt nichts, über dessen Verwendung if varein Konstrukt genauso gültig ist wie if let. Es gibt nichts an whereKlauseln, was meiner Meinung nach erwähnenswert ist, wenn es um das if letBinden geht (es entfernt in vielen Fällen eine ganze Verschachtelungsebene). Es gibt nichts über optionale Verkettung.
nhgrif

6
Und wenn Sie implizit entpackte Optionen erwähnen, erwähnen Sie nicht einmal, dass Sie alle oben genannten optionalen Tricks verwenden können, um implizit entpackte Optionen sicher auszupacken. Wir behandeln auch nicht das Auspacken mehrerer Optionen in derselben Klausel.
nhgrif

1
@nhgrif, ich habe gesagt "Sie könnten auch Try / Catch- oder Guard-Fehlerbehandlung verwenden, aber im Allgemeinen ist eine der anderen oben genannten Techniken sauberer." Sie haben sicherlich einige gute Punkte. Dies ist ein ziemlich großes Thema. Wie wäre es, wenn Sie Ihre eigene Antwort einbringen, die Dinge abdeckt, die ich nicht getan habe, anstatt Kommentare abzugeben? Auf diese Weise werden die Frage und ihre Antworten zu einem besseren Aktivposten für die Website.
Duncan C

13

Da die obigen Antworten klar erklären, wie man sicher mit Optionals spielt. Ich werde versuchen zu erklären, was Optionals wirklich schnell sind.

Eine andere Möglichkeit, eine optionale Variable zu deklarieren, ist

var i : Optional<Int>

Und der optionale Typ ist nichts anderes als eine Aufzählung mit zwei Fällen, dh

 enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none 
    case some(Wrapped)
    .
    .
    .
}

Also, um unserer Variablen 'i' eine Null zuzuweisen. Wir können tun var i = Optional<Int>.none oder einen Wert zuweisen, wir werden einen Wert übergeben var i = Optional<Int>.some(28)

Laut Swift ist 'Null' das Fehlen von Wert. Und um eine Instanz zu erstellen, die mit initialisiert wurde, müssen nilwir uns an ein Protokoll halten, das aufgerufen wird, ExpressibleByNilLiteralund wenn Sie es erraten haben, wird davon abgeraten , nur anderen Typen zu Optionalsentsprechen ExpressibleByNilLiteralund diesen zu entsprechen.

ExpressibleByNilLiteralhat eine einzige Methode namens, init(nilLiteral:)die ein Instace mit nil initialisiert. Normalerweise wird diese Methode nicht aufgerufen. Laut einer schnellen Dokumentation wird davon abgeraten, diesen Initialisierer direkt aufzurufen, wenn der Compiler ihn aufruft, wenn Sie einen optionalen Typ mit nilLiteral initialisieren .

Sogar ich muss meinen Kopf um Optionals wickeln (kein Wortspiel beabsichtigt): D Happy Swfting All .


12

Zunächst sollten Sie wissen, was ein optionaler Wert ist. Weitere Informationen finden Sie in der Programmiersprache Swift .

Zweitens sollten Sie wissen, dass der optionale Wert zwei Status hat. Einer ist der volle Wert und der andere ist ein Nullwert. Bevor Sie einen optionalen Wert implementieren, sollten Sie überprüfen, um welchen Status es sich handelt.

Sie können if let ...oder guard let ... elseund so weiter verwenden.

Wenn Sie den Variablenstatus vor Ihrer Implementierung nicht überprüfen möchten, können Sie ihn auch verwenden var buildingName = buildingName ?? "buildingName".


8

Ich hatte diesen Fehler einmal, als ich versuchte, meine Outlets-Werte aus der Methode zur Vorbereitung auf den Übergang wie folgt festzulegen:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let destination = segue.destination as? DestinationVC{

        if let item = sender as? DataItem{
            // This line pops up the error
            destination.nameLabel.text = item.name
        }
    }
}

Dann stellte ich fest, dass ich die Werte der Ziel-Controller-Ausgänge nicht einstellen kann, da der Controller noch nicht geladen oder initialisiert wurde.

Also habe ich es so gelöst:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let destination = segue.destination as? DestinationVC{

        if let item = sender as? DataItem{
            // Created this method in the destination Controller to update its outlets after it's being initialized and loaded
            destination.updateView(itemData:  item)
        }
    }
}

Ziel-Controller:

// This variable to hold the data received to update the Label text after the VIEW DID LOAD
var name = ""

// Outlets
@IBOutlet weak var nameLabel: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
    nameLabel.text = name
}

func updateView(itemDate: ObjectModel) {
    name = itemDate.name
}

Ich hoffe, diese Antwort hilft jedem da draußen mit dem gleichen Problem, bei dem ich festgestellt habe, dass die markierte Antwort eine großartige Ressource für das Verständnis der Optionen und ihrer Funktionsweise ist, aber das Problem selbst nicht direkt angesprochen hat.


Vergessen Sie nicht zu erwähnen, wo Sie updateViewden Ziel-Controller anrufen sollen ;)
Ahmad F

@ AhmadF guter Punkt. updateViewIn diesem Fall ist es jedoch nicht erforderlich , das im Ziel-Controller aufzurufen, da ich die nameVariable verwende, um das nameLabel.textim zu setzen viewDidLoad. Aber wenn wir viel einrichten würden, wäre es sicherlich besser, eine andere Funktion zu erstellen und sie viewDidLoadstattdessen von dort aus aufzurufen .
Wissa

7

Grundsätzlich haben Sie versucht, einen Nullwert an Stellen zu verwenden, an denen Swift nur Nicht-Nullwerte zulässt, indem Sie dem Compiler mitgeteilt haben, dass er Ihnen vertrauen soll, dass dort niemals ein Nullwert vorhanden sein wird, sodass Ihre App kompiliert werden kann.

Es gibt verschiedene Szenarien, die zu dieser Art von schwerwiegenden Fehlern führen:

  1. Zwangsauspacken:

    let user = someVariable!

    Wenn someVariableNull ist, dann bekommen Sie einen Absturz. Durch ein Force-Unwrap haben Sie die Null-Check-Verantwortung vom Compiler auf Sie übertragen. Durch ein erzwungenes Unwrap garantieren Sie dem Compiler, dass Sie dort niemals Null-Werte haben. Und raten Sie mal, was passiert, wenn ein Nullwert irgendwie endet someVariable?

    Lösung? Verwenden Sie die optionale Bindung (auch bekannt als if-let) und führen Sie dort die Variablenverarbeitung durch:

    if user = someVariable {
        // do your stuff
    }
  2. Zwangsabgüsse:

    let myRectangle = someShape as! Rectangle

    Hier weisen Sie den Compiler durch Force Casting an, sich keine Sorgen mehr zu machen, da Sie dort immer eine RectangleInstanz haben. Und solange das so ist, müssen Sie sich keine Sorgen machen. Die Probleme beginnen, wenn Sie oder Ihre Kollegen aus dem Projekt anfangen, Nicht-Rechteck-Werte zu verteilen.

    Lösung? Verwenden Sie die optionale Bindung (auch bekannt als if-let) und führen Sie dort die Variablenverarbeitung durch:

    if let myRectangle = someShape as? Rectangle {
        // yay, I have a rectangle
    }
  3. Implizit ausgepackte Optionen. Angenommen, Sie haben die folgende Klassendefinition:

    class User {
        var name: String!
    
        init() {
            name = "(unnamed)"
        }
    
        func nicerName() {
            return "Mr/Ms " + name
        }
    }

    Wenn nun niemand die nameEigenschaft durcheinander bringt, indem er sie auf setzt nil, funktioniert sie wie erwartet, wird jedoch Uservon einem JSON initialisiert, dem die fehltname Schlüssel , wird beim Versuch, die Eigenschaft zu verwenden, der schwerwiegende Fehler angezeigt.

    Lösung? Verwenden Sie sie nicht :) Es sei denn, Sie sind zu 102% sicher, dass die Eigenschaft zum Zeitpunkt ihrer Verwendung immer einen Wert ungleich Null hat. In den meisten Fällen funktioniert die Konvertierung in eine optionale oder nicht optionale Version. Wenn Sie es nicht optional machen, hilft Ihnen der Compiler auch dabei, indem Sie den Codepfaden mitteilen, die Sie verpasst haben, und dieser Eigenschaft einen Wert geben

  4. Nicht angeschlossene oder noch nicht angeschlossene Steckdosen. Dies ist ein besonderer Fall von Szenario 3. Grundsätzlich haben Sie eine XIB-geladene Klasse, die Sie verwenden möchten.

    class SignInViewController: UIViewController {
    
        @IBOutlet var emailTextField: UITextField!
    }

    Wenn Sie nun die Verbindung zum Outlet über den XIB-Editor verpasst haben, stürzt die App ab, sobald Sie den Outlet verwenden möchten. Lösung? Stellen Sie sicher, dass alle Steckdosen angeschlossen sind. Oder verwenden Sie den ?Operator : emailTextField?.text = "my@email.com". Oder deklarieren Sie den Ausgang als optional. In diesem Fall zwingt Sie der Compiler jedoch, den gesamten Code zu entpacken.

  5. Werte, die von Objective-C stammen und keine Annullierungsanmerkungen enthalten. Nehmen wir an, wir haben die folgende Objective-C-Klasse:

    @interface MyUser: NSObject
    @property NSString *name;
    @end

    Wenn nun keine Annullierungsanmerkungen angegeben werden (entweder explizit oder über NS_ASSUME_NONNULL_BEGIN/ NS_ASSUME_NONNULL_END), wird die nameEigenschaft in Swift as importiert String!(eine IUO - implizit entpackt optional). Sobald ein schneller Code den Wert verwenden möchte, stürzt er ab, wenn er gleich nameNull ist.

    Lösung? Fügen Sie Ihrem Objective-C-Code Anmerkungen zur Nullbarkeit hinzu. Beachten Sie jedoch, dass der Objective-C-Compiler in Bezug auf die Nullfähigkeit etwas freizügig ist. Möglicherweise erhalten Sie keine Werte, selbst wenn Sie diese explizit als markiert haben nonnull.


2

Dies ist eher ein wichtiger Kommentar und deshalb können implizit entpackte Optionen beim Debuggen von nilWerten täuschen .

Denken Sie an den folgenden Code: Er wird ohne Fehler / Warnungen kompiliert:

c1.address.city = c3.address.city

Zur Laufzeit wird jedoch der folgende Fehler ausgegeben : Schwerwiegender Fehler: Beim Auspacken eines optionalen Werts wurde unerwartet Null gefunden

Kannst du mir sagen, welches Objekt ist nil?

Das kannst du nicht!

Der vollständige Code wäre:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var c1 = NormalContact()
        let c3 = BadContact()

        c1.address.city = c3.address.city // compiler hides the truth from you and then you sudden get a crash
    }
}

struct NormalContact {
    var address : Address = Address(city: "defaultCity")
}

struct BadContact {
    var address : Address!
}

struct Address {
    var city : String
}

Kurz gesagt, wenn var address : Address!Sie verwenden, verbergen Sie die Möglichkeit, dass eine Variable nilvon anderen Lesern stammen kann. Und wenn es abstürzt, bist du wie "Was zum Teufel?! Meinaddress ist nicht optional. Warum stürze ich dann ab?!.

Daher ist es besser, als solches zu schreiben:

c1.address.city = c2.address!.city  // ERROR:  Fatal error: Unexpectedly found nil while unwrapping an Optional value 

Können Sie mir jetzt sagen, welches Objekt es war nil?

Diesmal wurde Ihnen der Code klarer gemacht. Sie können rationalisieren und denken, dass es wahrscheinlich der addressParameter ist, der gewaltsam ausgepackt wurde.

Der vollständige Code wäre:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var c1 = NormalContact()
        let c2 = GoodContact()

        c1.address.city = c2.address!.city
        c1.address.city = c2.address?.city // not compile-able. No deceiving by the compiler
        c1.address.city = c2.address.city // not compile-able. No deceiving by the compiler
        if let city = c2.address?.city {  // safest approach. But that's not what I'm talking about here. 
            c1.address.city = city
        }

    }
}

struct NormalContact {
    var address : Address = Address(city: "defaultCity")
}

struct GoodContact {
    var address : Address?
}

struct Address {
    var city : String
}

2

Die Fehler EXC_BAD_INSTRUCTIONund fatal error: unexpectedly found nil while implicitly unwrapping an Optional valueerscheinen am häufigsten, wenn Sie ein Storyboard deklariert haben @IBOutlet, aber nicht mit dem Storyboard verbunden sind .

Sie sollten auch lernen, wie Optionals funktionieren, wie in anderen Antworten erwähnt, aber dies ist das einzige Mal, das mir am häufigsten erscheint.


Sollte die @IBOutletUrsache dieses Fehlers nicht den schwerwiegenden Fehler haben: Unerwartet Null gefunden, während implizit eine optionale Wertversion des Fehlers entpackt wird?
pkamb

1
Das stimmt. Wenn ich die Antwort sende, habe ich das vielleicht gemeint und die erste schwerwiegende Fehlermeldung kopiert. Die Antwort von Hamish sieht diesbezüglich sehr vollständig aus.
Ale Mohamad

2

Wenn dieser Fehler in CollectionView auftritt, versuchen Sie, auch eine CustomCell-Datei und Custom xib zu erstellen.

Fügen Sie diesen Code in ViewDidLoad () bei mainVC hinzu.

    let nib = UINib(nibName: "CustomnibName", bundle: nil)
    self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")

0

Ich bin auf diesen Fehler gestoßen, als ich von einem Tabellenansichts-Controller zu einem Ansichts-Controller gewechselt bin, weil ich vergessen hatte, den benutzerdefinierten Klassennamen für den Ansichts-Controller im Haupt-Storyboard anzugeben.

Etwas Einfaches, das es wert ist, überprüft zu werden, ob alles andere in Ordnung aussieht

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.