Co oznacza „Błąd krytyczny: nieoczekiwanie znaleziono zero podczas rozpakowywania wartości opcjonalnej”?

Aug 24 2015

Mój program Swift ulega awarii EXC_BAD_INSTRUCTIONi 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

685 Hamish Aug 24 2015 at 02:10

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 .nonejest 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 przekazaniu Intprzez pomyłkę. Podobnie, bezpieczeństwo typów zapobiega przypadkowemu przekazaniu opcjonalnego Stringdo fragmentu kodu, który wymaga nieopcjonalnego String. 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 intwskaźnik zerowy, nie można użyć, więc potrzebna byłaby oddzielna zmienna (na przykład value: Inti isValid: Bool) lub wyznaczona wartość wartownicza (na przykład -1lub INT_MIN). Podejścia te są podatne na błędy, ponieważ łatwo zapomnieć o sprawdzeniu isValidlub 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, Optionalrozwiązują te problemy, wprowadzając specjalną, oddzielną nilwartość (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 anOptionalStringjest niltutaj, 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ą nilw 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 anOptionalIntzawiera wartość, zostanie rozpakowana i przypisana do nowej numberstał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 whereklauzuli.

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 ajest typem opcjonalnym i bjest tego samego typu co a(chociaż zwykle nie jest opcjonalny).

Zasadniczo pozwala powiedzieć „Jeśli azawiera wartość, rozpakuj ją. Jeśli tak się nie stanie, bzamiast tego wróci ”. Na przykład możesz tego użyć w ten sposób:

let number = anOptionalInt ?? 0

Spowoduje to zdefiniowanie numberstałej Inttypu, która będzie zawierała wartość anOptionalInt, jeśli zawiera wartość, lub w 0inny 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ą footypu opcjonalna Fooinstancja.

var foo : Foo?

Gdybyśmy chcieli wywołać metodę, fooktóra nic nie zwraca, możemy po prostu zrobić:

foo?.doSomethingInteresting()

Jeśli foozawiera 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 nilw 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 foojest 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 Voidmetoda, 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ż foojest 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ć foosię 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 fooma opcjonalną właściwość baz, która ma właściwość qux- możesz napisać:

let optionalQux = foo?.baz?.qux

Ponownie, ponieważ fooi bazsą opcjonalne, wartość zwracana z quxzawsze 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 mapi 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 mapfunkcję - możemy użyć stringByAppendingStringfunkcji w celu konkatenacji z innym ciągiem.

Ponieważ stringByAppendingStringprzyjmuje 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 stringByAppendingStringdo użycia, jeśli anOptionalStringma wartość.

Na przykład:

var anOptionalString:String? = "bar"

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

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

Jeśli jednak anOptionalStringnie ma wartości, mapzwróci nil. Na przykład:

var anOptionalString:String?

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

print(anOptionalString) // nil

flatMapdział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 catchbloku.

errorStały widać w catchbloku nie zostało uznane przez nas - to automatycznie generowane przez catch.

Możesz także zadeklarować errorsiebie, 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 trytego 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

65 Sajjon Dec 19 2016 at 20:26

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 letlubguard 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

40 DuncanC Feb 28 2016 at 21:37

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 fooopcjonalnym:

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.

13 Tharzeez Nov 17 2017 at 23:15

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ą nilMusimy dostosować się do protokołu o nazwie ExpressibleByNilLiterali świetnie, jeśli zgadłeś, odradza się tylko Optionalsdostosowywanie ExpressibleByNilLiterali dostosowywanie się do innych typów.

ExpressibleByNilLiteralma 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 nilliterałem.

Nawet ja muszę owinąć głowę (gra słów nie była zamierzona). Opcje: D Happy Swfting All .

12 QiunCheng Jul 30 2016 at 14:18

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 ... elsei tak dalej.

Z drugiej strony, jeśli nie chcesz sprawdzać stanu zmiennej przed implementacją, możesz użyć var buildingName = buildingName ?? "buildingName"zamiast tego.

8 Wissa Apr 13 2018 at 21:06

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.

7 Cristik Sep 19 2018 at 13:04

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:

  1. wymuszone rozpakowywanie:

    let user = someVariable!
    

    Jeśli someVariablejest 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ę na someVariable?

    Rozwiązanie? Użyj opcjonalnego wiązania (aka if-let), wykonaj tam przetwarzanie zmiennej:

    if user = someVariable {
        // do your stuff
    }
    
  2. 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 Rectangleinstancję. 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
    }
    
  3. 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 namewłaściwością, ustawiając ją na nil, to działa zgodnie z oczekiwaniami, jednak jeśli Userzostanie zainicjowany z JSON, który nie ma nameklucza, 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

  4. 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 nich emailTextField?.text = "[email protected]". Lub zadeklaruj wyjście jako opcjonalne, chociaż w tym przypadku kompilator zmusi cię do rozpakowania go w całym kodzie.

  5. 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), namewłaściwość zostanie zaimportowana w języku Swift jako String!(IUO - niejawnie rozpakowana opcja). Gdy tylko jakiś szybki kod będzie chciał użyć tej wartości, nastąpi awaria, jeśli namejest 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.

2 Honey Nov 01 2018 at 11:39

Jest to bardziej ważny komentarz i dlatego niejawnie rozpakowane opcje opcjonalne mogą być mylące, jeśli chodzi o debugowanie nilwartoś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ć nilz innymi czytelnikami. A kiedy się zawiesza, myślisz „co do cholery ?! Mój addressnie 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 addressparametr, 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
}
2 AleMohamad May 21 2019 at 00:34

Błędy EXC_BAD_INSTRUCTIONi fatal error: unexpectedly found nil while implicitly unwrapping an Optional valuepojawia 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.

2 ShigaSuresh Feb 13 2020 at 01:16

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")
MikeVolmar Sep 26 2019 at 22:11

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