Was bedeutet "Schwerwiegender Fehler: Beim Auspacken eines optionalen Werts unerwartet Null gefunden"?
Mein Swift-Programm stürzt mit EXC_BAD_INSTRUCTION
einem 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 .
Antworten
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 Optional<Wrapped>
handelt es sich um einen Optionstyp : Er kann einen beliebigen Wert aus dem ursprünglichen Typ ("Wrapped") oder überhaupt keinen Wert (den Sonderwert nil
) enthalten. Ein optionaler Wert muss entpackt werden, bevor er verwendet werden kann.
Optional ist ein generischer Typ , was bedeutet , dass Optional<Int>
und Optional<String>
sind verschiedene Typen - die Art innerhalb <>
des umschlossenen Typ genannt wird. Unter der Haube ist ein Optional eine Aufzählung mit zwei Fällen: .some(Wrapped)
und .none
, wo .none
entspricht nil
.
Optionale Optionen können mit dem angegebenen Typ Optional<T>
oder (am häufigsten) als Kurzform mit einem ?
Suffix deklariert werden .
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
var aVerboseOptionalInt: Optional<Int> // equivalent to `Int?`
anOptionalInt = nil // now this variable contains nil instead of an integer
Optionals sind ein einfaches, aber leistungsstarkes Tool, mit dem Sie Ihre Annahmen beim Schreiben von Code ausdrücken können. Der Compiler kann diese Informationen verwenden, um Fehler zu vermeiden. Aus der Swift-Programmiersprache :
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 a erfordert
String
, verhindert die Typensicherheit, dass Sie ihnInt
versehentlich weitergeben. Ebenso verhindert die Typensicherheit, dass Sie versehentlich ein optionales ElementString
an einen Code übergeben, für den ein nicht optionales Element erforderlich istString
. Die Typensicherheit hilft Ihnen, Fehler so früh wie möglich im Entwicklungsprozess zu erkennen und zu beheben.
Einige andere Programmiersprachen haben ebenfalls generische Optionstypen : z. B. Vielleicht in Haskell, Option in Rust und optional in C ++ 17.
In Programmiersprachen ohne Optionstypen wird häufig ein bestimmter "Sentinel" -Wert verwendet, um das Fehlen eines gültigen Werts anzuzeigen. In Objective-C beispielsweise repräsentiert nil
(der Nullzeiger ) das Fehlen eines Objekts. Für primitive Typen wie int
kann kein Nullzeiger verwendet werden, sodass Sie entweder eine separate Variable (wie value: Int
und isValid: Bool
) oder einen bestimmten Sentinel-Wert (wie -1
oder INT_MIN
) benötigen . Diese Ansätze sind fehleranfällig, da leicht vergessen wird, isValid
den Sentinel-Wert zu überprüfen oder zu überprüfen. Wenn ein bestimmter Wert als Sentinel ausgewählt wird, bedeutet dies, dass er nicht mehr als gültiger Wert behandelt werden kann.
Optionstypen wie Swift Optional
lösen diese Probleme, indem sie einen speziellen, separaten nil
Wert einführen (damit Sie keinen Sentinel-Wert festlegen müssen) und das System mit starken Typen nutzen, damit der Compiler Sie bei Bedarf daran erinnern kann, nach Null zu suchen.
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.
Dieser Absturz kann mit zwei verschiedenen Arten des Force-Unwraps auftreten:
1. Explizite Kraftentpackung
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 anOptionalString
ist nil
hier, 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 nil
zur 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.
Zwar kann es vorkommen, dass Sie das gewaltsame Auspacken verwenden können, da Sie wissen , dass ein optionales Element einen Wert enthält. Es gibt jedoch keinen einzigen 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 ausgepackte 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 {...}
oder if 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 anOptionalInt
ein Wert enthalten ist, wird er entpackt und der neuen number
Konstante 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 eliminieren , 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 where
Klausel.
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 a
es sich um einen optionalen Typ handelt und b
der gleiche Typ ist wie a
(obwohl normalerweise nicht optional).
Sie können im Wesentlichen sagen: „Wenn a
ein Wert enthalten ist, packen Sie ihn aus. Wenn dies nicht der Fall ist, kehren Sie b
stattdessen zurück. “ Zum Beispiel könnten Sie es so verwenden:
let number = anOptionalInt ?? 0
Dadurch wird eine number
Konstante vom Int
Typ definiert, die entweder den Wert von anOptionalInt
enthält, wenn sie einen Wert enthält, oder auf 0
andere 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 geschieht einfach, indem der Variablenname ?
bei Verwendung mit einem Suffix versehen wird .
Angenommen, wir haben eine Variable foo
vom Typ einer optionalen Foo
Instanz.
var foo : Foo?
Wenn wir eine Methode aufrufen wollten foo
, die nichts zurückgibt, können wir einfach Folgendes tun:
foo?.doSomethingInteresting()
Wenn foo
ein 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 nil
Objective-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 foo
ist 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 Void
fü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 foo
optional 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 foo
selbst 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 foo
eine optionale Eigenschaft haben baz
, die eine Eigenschaft qux
hat, Folgendes schreiben können:
let optionalQux = foo?.baz?.qux
Da foo
und baz
optional sind, ist der von zurückgegebene Wert qux
immer optional, unabhängig davon, ob er qux
selbst optional ist.
map
und flatMap
Eine häufig nicht genutzte Funktion mit Optionen ist die Möglichkeit, die Funktionen map
und zu flatMap
verwenden. Mit diesen können Sie nicht optionale Transformationen auf optionale Variablen anwenden. Wenn eine Option 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 map
Funktion darauf können wir die stringByAppendingString
Funktion verwenden, um sie mit einer anderen Zeichenfolge zu verketten.
Da stringByAppendingString
ein nicht optionales Zeichenfolgenargument verwendet wird, können wir unsere optionale Zeichenfolge nicht direkt eingeben. Mit using map
können wir jedoch allow stringByAppendingString
verwenden, wenn anOptionalString
ein Wert vorhanden ist.
Zum Beispiel:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
Wenn anOptionalString
jedoch kein Wert vorhanden ist, map
wird zurückgegeben nil
. Zum Beispiel:
var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
flatMap
funktioniert ähnlich wie map
, außer dass Sie eine weitere Option aus dem Verschlusskörper 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 catch
Block abgefangen .
Die error
Konstante, die Sie im catch
Block sehen, wurde von uns nicht deklariert - sie wird automatisch von generiert catch
.
Sie können sich auch error
selbst 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 try
diese Weise können Sie Fehler, die durch Wurffunktionen verursacht werden, richtig 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
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 let
oderguard 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 kurz und prägnant sein. Lesen Sie die akzeptierte Antwort , um sie vollständig zu verstehen
Ressourcen
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önnten 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 Wert darin 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 foo
optionalen:
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 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 "kapitalisiertes String" 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.
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
var i = Optional<Int>.none
einen Wert zuweisen oder übergeben, 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 nil
wir uns an ein Protokoll halten, das aufgerufen wird ExpressibleByNilLiteral
und großartig ist, wenn Sie es erraten haben. Es wird davon abgeraten , nur anderen Typen zu Optionals
entsprechen ExpressibleByNilLiteral
und diesen zu entsprechen.
ExpressibleByNilLiteral
hat 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 nil
Literal initialisieren .
Sogar ich muss meinen Kopf um Optionals wickeln (kein Wortspiel beabsichtigt): D Happy Swfting All .
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 ... else
und 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"
.
Ich hatte diesen Fehler einmal, als ich versuchte, meine Outlets-Werte aus der Methode "Vorbereitung auf Segue" 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.
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:
Zwangsauspacken:
let user = someVariable!
Wenn
someVariable
Null ist, dann bekommen Sie einen Absturz. Durch das erzwungene Entpacken haben Sie die Verantwortung für die Nullprüfung vom Compiler auf Sie verlagert. Durch das erzwungene Entpacken garantieren Sie dem Compiler, dass Sie dort niemals Nullwerte haben. Und raten Sie mal, was passiert, wenn ein Nullwert irgendwie endetsomeVariable
?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 }
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
Rectangle
Instanz 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 }
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
name
Eigenschaft durcheinander bringt, indem er sie auf setztnil
, funktioniert sie wie erwartet. Wenn sieUser
jedoch von einem JSON initialisiert wird, dem dername
Schlüssel fehlt , 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
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 für sie :emailTextField?.text = "[email protected]"
. Oder deklarieren Sie den Ausgang als optional. In diesem Fall zwingt Sie der Compiler jedoch, den gesamten Code zu entpacken.Werte, die von Objective-C stammen und keine Anmerkungen zur Nullbarkeit 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 diename
Eigenschaft in Swift alsString!
(eine IUO - implizit entpackt optional) importiert . Sobald ein schneller Code den Wert verwenden möchte, stürzt er ab, wenn ername
Null 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 Nullbarkeit ein wenig freizügig ist. Möglicherweise erhalten Sie keine Werte, selbst wenn Sie diese explizit als markiert haben
nonnull
.
Dies ist eher ein wichtiger Kommentar und deshalb können implizit entpackte Optionen beim Debuggen von nil
Werten 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 nil
von anderen Lesern stammen kann. Und wenn es abstürzt, sagst du: "Was zum Teufel?! Mein address
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 address
Parameter ist, der gewaltsam entpackt 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
}
Die Fehler EXC_BAD_INSTRUCTION
und fatal error: unexpectedly found nil while implicitly unwrapping an Optional value
erscheinen am häufigsten, wenn Sie eine 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.
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")
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