Co oznacza „Błąd krytyczny: nieoczekiwanie znaleziono zero podczas rozpakowywania wartości opcjonalnej”?
Mój program Swift ulega awarii EXC_BAD_INSTRUCTION
i występuje jeden z następujących podobnych błędów. Co oznacza ten błąd i jak go naprawić?
Błąd krytyczny: nieoczekiwanie znaleziono wartość nil podczas rozpakowywania wartości opcjonalnej
lub
Błąd krytyczny: nieoczekiwanie znaleziono wartość nil podczas niejawnego rozpakowywania wartości opcjonalnej
Ten post ma na celu zebranie odpowiedzi na problemy „nieoczekiwanie znalezione zero”, aby nie były rozproszone i trudne do znalezienia. Możesz dodać własną odpowiedź lub edytować istniejącą odpowiedź wiki.
Odpowiedzi
Ta odpowiedź to wiki społeczności . Jeśli uważasz, że można to ulepszyć, nie krępuj się edytować !
Tło: co jest opcjonalne?
W języku Swift Optional<Wrapped>
jest typem opcji : może zawierać dowolną wartość z typu oryginalnego („Wrapped”) lub nie zawierać żadnej wartości (wartość specjalna nil
). Opcjonalna wartość musi zostać rozpakowana, zanim będzie można jej użyć.
Opcjonalnie jest typ ogólny , co oznacza, że Optional<Int>
i Optional<String>
są różne typy - typ wewnątrz <>
nazywana jest owinięty typu. Pod maską Opcjonalne to wyliczenie z dwoma przypadkami: .some(Wrapped)
i .none
, gdzie .none
jest równoważne nil
.
Opcje mogą być deklarowane przy użyciu nazwanego typu Optional<T>
lub (najczęściej) jako skrót z ?
sufiksem.
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
Opcje są prostym, ale potężnym narzędziem do wyrażania twoich założeń podczas pisania kodu. Kompilator może wykorzystać te informacje, aby zapobiec popełnianiu błędów. Z języka szybkiego programowania :
Swift jest językiem bezpiecznym dla typów , co oznacza, że język pomaga jasno określić typy wartości, z którymi może pracować kod. Jeśli część twojego kodu wymaga
String
, typ bezpieczeństwa zapobiega jego przekazaniuInt
przez pomyłkę. Podobnie, bezpieczeństwo typów zapobiega przypadkowemu przekazaniu opcjonalnegoString
do fragmentu kodu, który wymaga nieopcjonalnegoString
. Bezpieczeństwo typów pomaga wychwytywać i naprawiać błędy na jak najwcześniejszym etapie procesu tworzenia.
Niektóre inne języki programowania również mają ogólne typy opcji : na przykład Może w Haskell, opcja w Rust i opcjonalne w C ++ 17.
W językach programowania bez typów opcji, określona wartość „wartownika” jest często używana do wskazania braku prawidłowej wartości. Na przykład w Objective-C nil
( wskaźnik zerowy ) reprezentuje brak obiektu. W przypadku typów prymitywnych, takich jak int
wskaźnik zerowy, nie można użyć, więc potrzebna byłaby oddzielna zmienna (na przykład value: Int
i isValid: Bool
) lub wyznaczona wartość wartownicza (na przykład -1
lub INT_MIN
). Podejścia te są podatne na błędy, ponieważ łatwo zapomnieć o sprawdzeniu isValid
lub sprawdzeniu wartości wartowniczej. Ponadto, jeśli dana wartość zostanie wybrana jako wartownik, oznacza to, że nie można jej już traktować jako prawidłowej wartości.
Typy opcji, takie jak Swift, Optional
rozwiązują te problemy, wprowadzając specjalną, oddzielną nil
wartość (więc nie musisz wyznaczać wartości wartowniczej) i wykorzystując silny system typów, aby kompilator mógł pomóc Ci pamiętać o sprawdzaniu zerowej, gdy jest to konieczne.
Dlaczego pojawił się „ błąd krytyczny: nieoczekiwanie znaleziono zero podczas rozpakowywania wartości opcjonalnej ”?
Aby uzyskać dostęp do wartości opcjonalnej (jeśli w ogóle ją ma), musisz ją rozpakować . Opcjonalną wartość można rozpakować bezpiecznie lub na siłę. Jeśli wymusisz rozpakowanie opcjonalnego i nie ma on wartości, twój program ulegnie awarii z powyższym komunikatem.
Xcode pokaże awarię, podświetlając wiersz kodu. Problem występuje na tej linii.
Ta awaria może wystąpić przy dwóch różnych rodzajach rozwijania siłowego:
1. Wyraźne rozpakowywanie siły
Odbywa się to za pomocą !
operatora na opcjonalnym. Na przykład:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
Błąd krytyczny: nieoczekiwanie znaleziono wartość nil podczas rozpakowywania wartości opcjonalnej
Jak anOptionalString
jest nil
tutaj, dostaniesz awarii na linii, gdzie można wymusić unwrap niego.
2. Niejawnie nieopakowane opcje
Są one definiowane za pomocą !
zamiast a ?
po typie.
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
Zakłada się, że te opcje zawierają wartość. Dlatego za każdym razem, gdy uzyskasz dostęp do niejawnie rozpakowanego elementu opcjonalnego, zostanie on automatycznie wymuszony rozpakowany. Jeśli nie zawiera wartości, ulegnie awarii.
print(optionalDouble) // <- CRASH
Błąd krytyczny: nieoczekiwanie znaleziono wartość nil podczas niejawnego rozpakowywania wartości opcjonalnej
Aby dowiedzieć się, która zmienna spowodowała awarię, możesz przytrzymać ⌥przycisk podczas klikania, aby wyświetlić definicję, gdzie możesz znaleźć opcjonalny typ.
W szczególności IBOutlety są zwykle niejawnie nieopakowanymi opcjami. Dzieje się tak, ponieważ twój xib lub storyboard połączy gniazda w czasie wykonywania, po inicjalizacji. Dlatego powinieneś upewnić się, że nie uzyskujesz dostępu do gniazd przed ich załadowaniem. Powinieneś także sprawdzić, czy połączenia są poprawne w pliku storyboard / xib, w przeciwnym razie wartości będą nil
w czasie wykonywania i dlatego ulegną awarii, gdy zostaną niejawnie rozpakowane . Podczas naprawiania połączeń spróbuj usunąć wiersze kodu definiujące gniazda, a następnie połącz je ponownie.
Kiedy powinienem wymusić rozpakowanie elementu opcjonalnego?
Explicit Force Unwrapping
Zgodnie z ogólną zasadą, nigdy nie należy jawnie wymuszać rozpakowywania opcjonalnego za pomocą !
operatora. Może się zdarzyć, że użycie !
jest dopuszczalne - ale powinieneś go używać tylko wtedy, gdy masz 100% pewności, że opcja zawiera wartość.
Chociaż może zaistnieć sytuacja, w której możesz użyć wymuszonego rozpakowywania, jak wiesz na pewno , że opcja zawiera wartość - nie ma ani jednego miejsca, w którym nie można bezpiecznie odpakować tej opcji.
Niejawnie nieopakowane opcje
Te zmienne są zaprojektowane tak, aby można było odłożyć ich przypisanie do późniejszej części kodu. To jest twój obowiązek upewnić się, że posiadają wartość, zanim do nich dostęp. Jednakże, ponieważ wiążą się one z rozpakowywaniem siły, nadal są z natury niebezpieczne - ponieważ zakładają , że twoja wartość jest różna od zera, nawet jeśli przypisanie nil jest prawidłowe.
Powinieneś używać niejawnie rozpakowanych opcji opcjonalnych tylko w ostateczności . Jeśli możesz użyć leniwej zmiennej lub podać domyślną wartość zmiennej - powinieneś to zrobić zamiast używać niejawnie rozpakowanej zmiennej opcjonalnej.
Istnieje jednak kilka scenariuszy, w których niejawnie rozpakowane opcje opcjonalne są korzystne i nadal możesz korzystać z różnych sposobów ich bezpiecznego rozpakowywania, jak podano poniżej - ale zawsze powinieneś ich używać z należytą ostrożnością.
Jak mogę bezpiecznie radzić sobie z opcjami?
Najprostszym sposobem sprawdzenia, czy opcja zawiera wartość, jest porównanie jej z nil
.
if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
Jednak w 99,9% przypadków podczas pracy z opcjami faktycznie będziesz chciał uzyskać dostęp do wartości, którą zawiera, jeśli w ogóle ją zawiera. Aby to zrobić, możesz użyć opcjonalnego powiązania .
Opcjonalne wiązanie
Opcjonalne powiązanie umożliwia sprawdzenie, czy opcja zawiera wartość - i umożliwia przypisanie nieopakowanej wartości do nowej zmiennej lub stałej. Używa składni if let x = anOptional {...}
lub if var x = anOptional {...}
, w zależności od tego, czy musisz zmodyfikować wartość nowej zmiennej po jej powiązaniu.
Na przykład:
if let number = anOptionalInt {
print("Contains a value! It is \(number)!")
} else {
print("Doesn’t contain a number")
}
Najpierw sprawdź, czy opcja zawiera wartość. Jeśli to robi , to „rozpakować” wartość jest przypisana do nowej zmiennej ( number
) - który można następnie dowolnie wykorzystać tak, jakby były non-opcjonalne. Jeśli opcja nie zawiera wartości, zostanie wywołana klauzula else, zgodnie z oczekiwaniami.
Zaletą opcjonalnego wiązania jest to, że możesz rozpakować wiele opcji jednocześnie. Możesz po prostu oddzielić instrukcje przecinkiem. Instrukcja zakończy się powodzeniem, jeśli wszystkie opcje zostaną rozpakowane.
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")
}
Inną fajną sztuczką jest to, że po rozpakowaniu możesz również użyć przecinków, aby sprawdzić określony warunek wartości.
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
Jedynym haczykiem związanym z użyciem opcjonalnego powiązania w instrukcji if jest to, że dostęp do nieopakowanej wartości można uzyskać tylko z zakresu instrukcji. Jeśli potrzebujesz dostępu do wartości spoza zakresu instrukcji, możesz użyć instrukcji guard .
Oświadczenie osłona pozwala zdefiniować warunek sukcesu - a obecny zakres będzie tylko kontynuować wykonywanie jeśli warunek jest spełniony. Są definiowane za pomocą składni guard condition else {...}
.
Aby więc użyć ich z opcjonalnym wiązaniem, możesz to zrobić:
guard let number = anOptionalInt else {
return
}
(Zauważ, że w treści strażnika musisz użyć jednego z poleceń przelewu kontrolnego , aby wyjść z zakresu aktualnie wykonywanego kodu).
Jeśli anOptionalInt
zawiera wartość, zostanie rozpakowana i przypisana do nowej number
stałej. Kod za strażnikiem będzie dalej wykonywany. Jeśli nie zawiera wartości - strażnik wykona kod w nawiasach, co doprowadzi do przeniesienia kontroli, aby kod bezpośrednio po nim nie został wykonany.
Naprawdę fajną rzeczą w instrukcjach guard jest to, że rozpakowana wartość jest teraz dostępna do użycia w kodzie następującym po instrukcji (ponieważ wiemy, że przyszły kod może zostać wykonany tylko wtedy, gdy opcja ma wartość). Jest to świetne rozwiązanie do eliminacji „piramid zagłady” utworzonych przez zagnieżdżanie wielu instrukcji if.
Na przykład:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
Strażnicy obsługują również te same zgrabne sztuczki, które obsługiwała instrukcja if, takie jak rozpakowywanie wielu opcji w tym samym czasie i używanie where
klauzuli.
To, czy używasz instrukcji if, czy guard, zależy całkowicie od tego, czy jakikolwiek przyszły kod wymaga, aby opcja opcjonalna zawierała wartość.
Operator łączenia zerowego
Nil zlewający Operator jest fajną wersję skróconą z potrójnego operatora warunkowego , przeznaczony przede wszystkim do konwersji ewentualne, aby non-optionals. Ma składnię a ?? b
, gdzie a
jest typem opcjonalnym i b
jest tego samego typu co a
(chociaż zwykle nie jest opcjonalny).
Zasadniczo pozwala powiedzieć „Jeśli a
zawiera wartość, rozpakuj ją. Jeśli tak się nie stanie, b
zamiast tego wróci ”. Na przykład możesz tego użyć w ten sposób:
let number = anOptionalInt ?? 0
Spowoduje to zdefiniowanie number
stałej Int
typu, która będzie zawierała wartość anOptionalInt
, jeśli zawiera wartość, lub w 0
inny sposób.
To jest po prostu skrótowe dla:
let number = anOptionalInt != nil ? anOptionalInt! : 0
Opcjonalne łączenie
Możesz użyć opcjonalnego łańcucha , aby wywołać metodę lub uzyskać dostęp do właściwości na opcjonalnym. Odbywa się to po prostu przez dodanie sufiksu do nazwy zmiennej ?
podczas jej używania.
Na przykład, powiedzmy, że mamy zmienną foo
typu opcjonalna Foo
instancja.
var foo : Foo?
Gdybyśmy chcieli wywołać metodę, foo
która nic nie zwraca, możemy po prostu zrobić:
foo?.doSomethingInteresting()
Jeśli foo
zawiera wartość, ta metoda zostanie wywołana na nim. Jeśli tak się nie stanie, nic złego się nie stanie - kod będzie po prostu kontynuował wykonywanie.
(Jest to podobne zachowanie do wysyłania wiadomości do nil
w celu C)
Dlatego może być również używany do ustawiania właściwości, a także do wywoływania metod. Na przykład:
foo?.bar = Bar()
Znowu nic złego się tutaj nie stanie, jeśli tak foo
jest nil
. Twój kod będzie po prostu kontynuował wykonywanie.
Kolejną fajną sztuczką, którą pozwala opcjonalne tworzenie łańcuchów, jest sprawdzenie, czy ustawienie właściwości lub wywołanie metody zakończyło się sukcesem. Możesz to zrobić, porównując zwracaną wartość z nil
.
(Dzieje się tak, ponieważ zwrócona zostanie opcjonalna wartość, Void?
a nie Void
metoda, która nic nie zwraca)
Na przykład:
if (foo?.bar = Bar()) != nil {
print("bar was set successfully")
} else {
print("bar wasn’t set successfully")
}
Jednak sytuacja staje się nieco trudniejsza, gdy próbujesz uzyskać dostęp do właściwości lub wywołać metody, które zwracają wartość. Ponieważ foo
jest opcjonalne, wszystko, co z niego zostanie zwrócone, będzie również opcjonalne. Aby sobie z tym poradzić, możesz albo rozpakować opcje, które są zwracane przy użyciu jednej z powyższych metod - albo rozpakować foo
się przed uzyskaniem dostępu do metod lub wywołaniem metod, które zwracają wartości.
Ponadto, jak sama nazwa wskazuje, można „połączyć” te stwierdzenia razem. Oznacza to, że jeśli foo
ma opcjonalną właściwość baz
, która ma właściwość qux
- możesz napisać:
let optionalQux = foo?.baz?.qux
Ponownie, ponieważ foo
i baz
są opcjonalne, wartość zwracana z qux
zawsze będzie opcjonalna, niezależnie od tego qux
, czy sama jest opcjonalna.
map
i flatMap
Często niedostatecznie wykorzystywaną funkcją z opcjami jest możliwość korzystania z funkcji map
i flatMap
. Umożliwiają one stosowanie nieopcjonalnych przekształceń do opcjonalnych zmiennych. Jeśli opcja ma wartość, możesz zastosować do niej daną transformację. Jeśli nie ma wartości, pozostanie nil
.
Na przykład, powiedzmy, że masz opcjonalny ciąg:
let anOptionalString:String?
Stosując do niego map
funkcję - możemy użyć stringByAppendingString
funkcji w celu konkatenacji z innym ciągiem.
Ponieważ stringByAppendingString
przyjmuje nieopcjonalny argument w postaci ciągu, nie możemy bezpośrednio wprowadzić naszego opcjonalnego ciągu. Jednak używając map
, możemy użyć allow stringByAppendingString
do użycia, jeśli anOptionalString
ma wartość.
Na przykład:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
Jeśli jednak anOptionalString
nie ma wartości, map
zwróci nil
. Na przykład:
var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
flatMap
działa podobnie map
, z tą różnicą, że umożliwia zwrócenie innego opcjonalnego z wnętrza ciała zamknięcia. Oznacza to, że możesz wprowadzić opcjonalne do procesu, który wymaga nie opcjonalnych danych wejściowych, ale może sam wyprowadzić opcjonalny.
try!
System obsługi błędów Swift może być bezpiecznie używany z Do-Try-Catch :
do {
let result = try someThrowingFunc()
} catch {
print(error)
}
Jeśli someThrowingFunc()
zgłosi błąd, zostanie on bezpiecznie przechwycony w catch
bloku.
error
Stały widać w catch
bloku nie zostało uznane przez nas - to automatycznie generowane przez catch
.
Możesz także zadeklarować error
siebie, ma tę zaletę, że może przesłać go do użytecznego formatu, na przykład:
do {
let result = try someThrowingFunc()
} catch let error as NSError {
print(error.debugDescription)
}
Korzystanie z try
tego sposobu jest właściwym sposobem próbowania, wychwytywania i obsługi błędów pochodzących z funkcji rzucających.
Jest też to, try?
co pochłania błąd:
if let result = try? someThrowingFunc() {
// cool
} else {
// handle the failure, but there's no error information available
}
Ale system obsługi błędów Swift umożliwia także „wymuszenie próby” za pomocą try!
:
let result = try! someThrowingFunc()
Koncepcje wyjaśnione w tym poście mają również zastosowanie tutaj: jeśli zostanie zgłoszony błąd, aplikacja ulegnie awarii.
Powinieneś używać tylko try!
wtedy, gdy możesz udowodnić, że jego wynik nigdy nie zawiedzie w twoim kontekście - a to jest bardzo rzadkie.
W większości przypadków będziesz korzystać z pełnego systemu Do-Try-Catch - i opcjonalnego try?
, w rzadkich przypadkach, gdy obsługa błędu nie jest ważna.
Zasoby
Odpowiedź TL; DR
Z nielicznymi wyjątkami ta zasada jest złota:
Unikaj używania !
Zadeklaruj zmienną opcjonalną ( ?
), a nie niejawnie rozpakowaną opcją (IUO) ( !
)
Innymi słowy, raczej użyj:
var nameOfDaughter: String?
Zamiast:
var nameOfDaughter: String!
Rozpakuj zmienną opcjonalną za pomocą if let
lubguard let
Albo rozpakuj zmienną jak ta:
if let nameOfDaughter = nameOfDaughter {
print("My daughters name is: \(nameOfDaughter)")
}
Lub tak:
guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")
Ta odpowiedź miała być zwięzła, dla pełnego zrozumienia przeczytaj zaakceptowaną odpowiedź
Zasoby
To pytanie pojawia się CAŁY CZAS w SO. To jedna z pierwszych rzeczy, z którymi zmagają się nowi programiści Swift.
Tło:
Swift używa pojęcia „Opcje”, aby radzić sobie z wartościami, które mogą zawierać wartość lub nie. W innych językach, takich jak C, możesz przechowywać wartość 0 w zmiennej, aby wskazać, że nie zawiera ona żadnej wartości. A co, jeśli 0 jest prawidłową wartością? Wtedy możesz użyć -1. A co, jeśli -1 jest prawidłową wartością? I tak dalej.
Opcje opcjonalne Swift pozwalają ustawić zmienną dowolnego typu tak, aby zawierała prawidłową wartość lub nie zawierała żadnej wartości.
Umieszczasz znak zapytania po typie, kiedy deklarujesz zmienną jako średnią (typ x lub brak wartości).
Opcjonalne jest w rzeczywistości kontenerem, który zawiera albo zmienną danego typu, albo nic.
Element opcjonalny musi zostać „rozpakowany”, aby można było pobrać jego wartość.
Znak „!” operator jest operatorem wymuszającym rozwijanie. Mówi „zaufaj mi. Wiem, co robię. Gwarantuję, że po uruchomieniu tego kodu zmienna nie będzie zawierała nil”. Jeśli się mylisz, upadasz.
O ile naprawdę nie wiesz, co robisz, unikaj znaku „!” wymuś operator rozwijania. Jest to prawdopodobnie największe źródło awarii dla początkujących programistów Swift.
Jak radzić sobie z opcjami:
Istnieje wiele innych sposobów radzenia sobie z bezpieczniejszymi opcjami. Oto kilka (lista nie jest wyczerpująca)
Możesz użyć „opcjonalnego wiązania” lub „if let” to say ”, jeśli to opcjonalne zawiera wartość, zapisz tę wartość w nowej, nieopcjonalnej zmiennej. Jeśli opcjonalna nie zawiera wartości, pomiń treść tej instrukcji if ”.
Oto przykład opcjonalnego wiązania z naszym foo
opcjonalnym:
if let newFoo = foo //If let is called optional binding. {
print("foo is not nil")
} else {
print("foo is nil")
}
Zwróć uwagę, że zmienna, którą definiujesz podczas korzystania z opcjonalnego biding, istnieje (jest tylko „w zakresie”) tylko w treści instrukcji if.
Alternatywnie możesz użyć instrukcji guard, która pozwala wyjść z funkcji, jeśli zmienna ma wartość nil:
func aFunc(foo: Int?) {
guard let newFoo = input else { return }
//For the rest of the function newFoo is a non-optional var
}
Instrukcje Guard zostały dodane w Swift 2. Guard pozwala zachować „złotą ścieżkę” w kodzie i uniknąć stale rosnących poziomów zagnieżdżonych ifów, które czasami wynikają z użycia opcjonalnego wiązania „if let”.
Istnieje również konstrukcja zwana „operatorem koalescencji zerowej”. Ma postać „zmienna_opcjonalna ?? wartość_zastępcza”. Zwraca nieopcjonalną zmienną o tym samym typie, co dane zawarte w opcjonalnej. Jeśli opcjonalne zawiera nil, zwraca wartość wyrażenia po znaku „??” symbol.
Możesz więc użyć takiego kodu:
let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")
Możesz także skorzystać z obsługi błędów try / catch lub guard, ale ogólnie jedna z pozostałych technik jest czystsza.
EDYTOWAĆ:
Innym, nieco bardziej subtelnym problemem z opcjami jest „niejawnie rozpakowane opcje opcjonalne. Gdy deklarujemy foo, możemy powiedzieć:
var foo: String!
W takim przypadku foo jest nadal opcjonalne, ale nie musisz go rozpakowywać, aby się do niego odwołać. Oznacza to, że za każdym razem, gdy próbujesz odwołać się do foo, zawiesza się, jeśli jest zero.
Więc ten kod:
var foo: String!
let upperFoo = foo.capitalizedString
Awaria przy odwołaniu do właściwości capitalizedString foo, nawet jeśli nie rozpakowujemy foo na siłę. wydruk wygląda dobrze, ale tak nie jest.
Dlatego chcesz być naprawdę ostrożny z niejawnie rozpakowanymi opcjami. (a może nawet całkowicie ich unikaj, dopóki nie będziesz dobrze rozumieć opcji).
Konkluzja: Kiedy po raz pierwszy uczysz się Swifta, udawaj znak „!” znak nie jest częścią języka. Może to sprawić, że będziesz miał kłopoty.
Ponieważ powyższe odpowiedzi jasno wyjaśniają, jak bezpiecznie grać z opcjami. Spróbuję wyjaśnić, jakie opcje są naprawdę szybkie.
Innym sposobem zadeklarowania opcjonalnej zmiennej jest
var i : Optional<Int>
Typ opcjonalny to nic innego jak wyliczenie z dwoma przypadkami, tj
enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
case some(Wrapped)
.
.
.
}
Zatem przypisanie zeru do naszej zmiennej „i”. Możemy zrobić
var i = Optional<Int>.none
lub przypisać wartość, przekażemy jakąś wartość
var i = Optional<Int>.some(28)
Według Swifta „zero” oznacza brak wartości. Aby utworzyć instancję zainicjowaną za pomocą nil
Musimy dostosować się do protokołu o nazwie ExpressibleByNilLiteral
i świetnie, jeśli zgadłeś, odradza się tylko Optionals
dostosowywanie ExpressibleByNilLiteral
i dostosowywanie się do innych typów.
ExpressibleByNilLiteral
ma jedną wywoływaną metodę, init(nilLiteral:)
która inicjalizuje wystąpienie wartością nil. Zwykle nie wywołujesz tej metody i zgodnie z szybką dokumentacją odradza się wywoływanie tego inicjatora bezpośrednio, ponieważ kompilator wywołuje go za każdym razem, gdy inicjujesz typ opcjonalny z nil
literałem.
Nawet ja muszę owinąć głowę (gra słów nie była zamierzona). Opcje: D Happy Swfting All .
Po pierwsze, powinieneś wiedzieć, co to jest wartość opcjonalna. Aby uzyskać szczegółowe informacje, możesz przejść do Szybkiego języka programowania .
Po drugie, powinieneś wiedzieć, że opcjonalna wartość ma dwa statusy. Jedna to pełna wartość, a druga to zero. Dlatego przed zaimplementowaniem opcjonalnej wartości należy sprawdzić, w jakim jest ona stanie.
Możesz użyć if let ...
lub guard let ... else
i tak dalej.
Z drugiej strony, jeśli nie chcesz sprawdzać stanu zmiennej przed implementacją, możesz użyć var buildingName = buildingName ?? "buildingName"
zamiast tego.
Miałem ten błąd raz, gdy próbowałem ustawić wartości moich Outlets z metody przygotowania do segue w następujący sposób:
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
}
}
}
Wtedy dowiedziałem się, że nie mogę ustawić wartości wyjść kontrolera docelowego, ponieważ kontroler nie został jeszcze załadowany ani zainicjowany.
Więc rozwiązałem to w ten sposób:
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)
}
}
}
Kontroler miejsca docelowego:
// 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
}
Mam nadzieję, że ta odpowiedź pomoże każdemu z tym samym problemem, co moim zdaniem, że zaznaczona odpowiedź jest świetnym źródłem informacji na temat opcji i sposobu ich działania, ale nie rozwiązała bezpośrednio problemu.
Zasadniczo próbowałeś użyć wartości zerowej w miejscach, w których Swift dopuszcza tylko wartości niezerowe, mówiąc kompilatorowi, aby zaufał ci, że nigdy nie będzie tam wartości zerowej, co pozwoliło twojej aplikacji na kompilację.
Istnieje kilka scenariuszy, które prowadzą do tego rodzaju błędu krytycznego:
wymuszone rozpakowywanie:
let user = someVariable!
Jeśli
someVariable
jest zero, nastąpi awaria. Wykonując wymuszone rozpakowanie, przeniosłeś odpowiedzialność za sprawdzenie zerowe z kompilatora na siebie, zasadniczo wykonując wymuszone rozpakowanie, gwarantujesz kompilatorowi, że nigdy nie będziesz miał tam wartości zerowych. I zgadnij, co się stanie, jeśli w jakiś sposób wartość zerowa kończy się nasomeVariable
?Rozwiązanie? Użyj opcjonalnego wiązania (aka if-let), wykonaj tam przetwarzanie zmiennej:
if user = someVariable { // do your stuff }
wymuszone (w dół) rzuty:
let myRectangle = someShape as! Rectangle
Tutaj przez wymuszenie rzucania mówisz kompilatorowi, aby się nie martwił, ponieważ zawsze będziesz mieć tam
Rectangle
instancję. Dopóki to trwa, nie musisz się martwić. Problemy zaczynają się, gdy Ty lub Twoi koledzy z projektu zaczynacie obiegać wartości inne niż prostokątne.Rozwiązanie? Użyj opcjonalnego wiązania (aka if-let), wykonaj tam przetwarzanie zmiennej:
if let myRectangle = someShape as? Rectangle { // yay, I have a rectangle }
Niejawnie rozpakowane opcje. Załóżmy, że masz następującą definicję klasy:
class User { var name: String! init() { name = "(unnamed)" } func nicerName() { return "Mr/Ms " + name } }
Teraz, jeśli nikt nie pomyli się z
name
właściwością, ustawiając ją nanil
, to działa zgodnie z oczekiwaniami, jednak jeśliUser
zostanie zainicjowany z JSON, który nie maname
klucza, podczas próby użycia właściwości pojawi się błąd krytyczny.Rozwiązanie? Nie używaj ich :) Chyba że masz 102% pewności, że właściwość zawsze będzie miała wartość różną od zera do czasu, gdy będzie potrzebna. W większości przypadków konwersja na opcjonalną lub nieopcjonalną będzie działać. Uczynienie go niepobocznym spowoduje również, że kompilator pomoże ci, wskazując ścieżki kodu, które przegapiłeś, i nadając wartość tej właściwości
Gniazda niepodłączone lub jeszcze niepołączone. To jest szczególny przypadek scenariusza nr 3. Zasadniczo masz jakąś klasę ładowaną przez XIB, której chcesz użyć.
class SignInViewController: UIViewController { @IBOutlet var emailTextField: UITextField! }
Teraz, jeśli przegapiłeś podłączenie gniazdka z edytora XIB, aplikacja ulegnie awarii, gdy tylko będziesz chciał użyć gniazdka. Rozwiązanie? Upewnij się, że wszystkie gniazda są podłączone. Lub użyj
?
operatora na nichemailTextField?.text = "[email protected]"
. Lub zadeklaruj wyjście jako opcjonalne, chociaż w tym przypadku kompilator zmusi cię do rozpakowania go w całym kodzie.Wartości pochodzące z Objective-C i które nie mają adnotacji o wartości null. Załóżmy, że mamy następującą klasę Objective-C:
@interface MyUser: NSObject @property NSString *name; @end
Teraz, jeśli nie określono adnotacji dopuszczających wartość null (jawnie lub za pośrednictwem
NS_ASSUME_NONNULL_BEGIN
/NS_ASSUME_NONNULL_END
),name
właściwość zostanie zaimportowana w języku Swift jakoString!
(IUO - niejawnie rozpakowana opcja). Gdy tylko jakiś szybki kod będzie chciał użyć tej wartości, nastąpi awaria, jeśliname
jest zerowa.Rozwiązanie? Dodaj adnotacje o wartości null do kodu celu. Uważaj jednak, kompilator Objective-C jest trochę pobłażliwy, jeśli chodzi o zerową wartość, możesz skończyć z wartościami zerowymi, nawet jeśli wyraźnie oznaczysz je jako
nonnull
.
Jest to bardziej ważny komentarz i dlatego niejawnie rozpakowane opcje opcjonalne mogą być mylące, jeśli chodzi o debugowanie nil
wartości.
Pomyśl o następującym kodzie: kompiluje się bez błędów / ostrzeżeń:
c1.address.city = c3.address.city
Jednak w czasie wykonywania wyświetla następujący błąd: Błąd krytyczny: nieoczekiwanie znaleziono zero podczas rozpakowywania wartości opcjonalnej
Czy możesz mi powiedzieć, który to obiekt nil
?
Nie możesz!
Pełny kod to:
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
}
Krótko mówiąc, używając var address : Address!
jesteś ukrywanie możliwość, że zmienna może być nil
z innymi czytelnikami. A kiedy się zawiesza, myślisz „co do cholery ?! Mój address
nie jest opcjonalny, więc dlaczego się zawieszam?!.
Dlatego lepiej pisać tak:
c1.address.city = c2.address!.city // ERROR: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Czy możesz mi teraz powiedzieć, który to był obiekt nil
?
Tym razem kod stał się dla Ciebie bardziej przejrzysty. Możesz zracjonalizować i pomyśleć, że prawdopodobnie jest to address
parametr, który został na siłę rozpakowany.
Pełny kod to:
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
}
Błędy EXC_BAD_INSTRUCTION
i fatal error: unexpectedly found nil while implicitly unwrapping an Optional value
pojawia się najczęściej, gdy zadeklarowałeś @IBOutlet
, ale nie masz połączenia ze scenorysem .
Powinieneś także dowiedzieć się, jak działają opcje , wspomniane w innych odpowiedziach, ale jest to jedyny przypadek, który wydaje mi się głównie.
Jeśli pojawi się ten błąd w CollectionView, spróbuj również utworzyć plik CustomCell i niestandardowy xib.
dodaj ten kod w ViewDidLoad () w mainVC.
let nib = UINib(nibName: "CustomnibName", bundle: nil)
self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")
Natknąłem się na ten błąd podczas przechodzenia z kontrolera widoku tabeli do kontrolera widoku, ponieważ zapomniałem określić nazwę klasy niestandardowej dla kontrolera widoku w głównym scenariuszu.
Coś prostego, co warto sprawdzić, czy wszystko inne wygląda dobrze