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

415

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 zero podczas rozpakowywania wartości opcjonalnej

lub

Błąd krytyczny: nieoczekiwanie znaleziono zero 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. Dodaj własną odpowiedź lub edytuj istniejącą odpowiedź wiki.

pkamb
źródło
8
@RobertColumbia, jest to para pytań i odpowiedzi na Wiki Wiki z obszerną dokumentacją problemu, nie sądzę, że to pytanie powinno zostać zamknięte z powodu braku MCVE.
JAL
Utwórz zmienną taką jak ta [var nameOfDaughter: String? ] i użyj tej zmiennej w ten sposób [jeśli let _ = nameOfDaughter {}] jest najlepszym podejściem.
iOS

Odpowiedzi:

673

Ta odpowiedź to wiki społeczności . Jeśli uważasz, że można to poprawić, możesz go edytować !

Tło: co jest opcjonalne?

W Swift Optionaljest typem ogólnym, który może zawierać wartość (dowolnego rodzaju) lub nie zawierać żadnej wartości.

W wielu innych językach programowania często stosuje się określoną wartość „wartownika”, aby wskazać brak wartości . Na przykład w Objective-C nil( wskaźnik zerowy ) wskazuje na brak obiektu. Ale staje się to trudniejsze podczas pracy z typami pierwotnymi - czy powinno się -1go używać do wskazywania braku liczby całkowitej, a może INT_MINinnej liczby całkowitej? Jeśli jakakolwiek konkretna wartość zostanie wybrana jako „brak liczby całkowitej”, oznacza to, że nie można jej już traktować jako wartości prawidłowej .

Swift jest językiem bezpiecznym dla typu, co oznacza, że ​​język ten pomaga w zrozumieniu typów wartości, z którymi może współpracować kod. Jeśli część kodu oczekuje ciągu, bezpieczeństwo typu zapobiega przypadkowemu przekazaniu go jako ciągu.

W Swift każdy typ może być opcjonalny . Opcjonalna wartość może przyjmować dowolną wartość z typu oryginalnego lub wartość specjalną nil.

Opcje są definiowane za pomocą ?przyrostka na typ:

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

Brak wartości opcjonalnej jest wskazywany przez nil:

anOptionalInt = nil

(Zauważ, że to nilnie jest to samo, co nilw Objective-C. W Objective-C niloznacza brak prawidłowego wskaźnika obiektu ; w Swift, Optionals nie są ograniczone do obiektów / typów referencji. Opcjonalne zachowuje się podobnie do Może Haskella .)


Dlaczego dostałem „ 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ą posiada), musisz ją rozpakować . Opcjonalną wartość można bezpiecznie rozpakować lub wymusić. Jeśli wymusisz rozpakowanie opcjonalnego, a nie miał on wartości, twój program zawiesi się z powyższym komunikatem.

Xcode pokaże awarię, podświetlając wiersz kodu. Problem występuje na tej linii.

awaria linii

Ta awaria może wystąpić z dwoma różnymi rodzajami rozpakowywania wymuszonego:

1. Rozpakowywanie z jawną siłą

Odbywa się to z !operatorem na opcjonalnym. Na przykład:

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

Błąd krytyczny: nieoczekiwanie znaleziono zero podczas rozpakowywania wartości opcjonalnej

Jak anOptionalStringjestnil tutaj, dostaniesz awarii na linii, gdzie można wymusić unwrap niego.

2. Niejawnie nieopakowane opcje

Są one zdefiniowane za pomocą !, a nie ?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 uzyskujesz dostęp do domyślnie nieopakowanego opcjonalnego, zostanie ono automatycznie wymuszone. Jeśli nie zawiera wartości, nastąpi awaria.

print(optionalDouble) // <- CRASH

Błąd krytyczny: nieoczekiwanie znaleziono zero podczas niejawnego rozpakowywania wartości opcjonalnej

Aby ustalić, która zmienna spowodowała awarię, możesz przytrzymać , klikając, aby wyświetlić definicję, w której możesz znaleźć typ opcjonalny.

W szczególności IBOutlety są zwykle niejawnie nieopakowanymi opcjami. Wynika to z faktu, że twój Xib lub Storyboard połączy się z gniazdami w czasie wykonywania, po inicjalizacji. Dlatego należy upewnić się, że nie uzyskuje się dostępu do gniazd przed ich załadowaniem. Należy również sprawdzić, czy połączenia są prawidłowe w pliku scenorysowym / pliku Xib, w przeciwnym razie wartości będą nilw czasie wykonywania, a zatem ulegną awarii, gdy zostaną niejawnie rozpakowane . Podczas ustalania połączeń spróbuj usunąć wiersze kodu definiujące gniazda, a następnie podłącz je ponownie.


Kiedy powinienem wymusić rozpakowanie Opcjonalnego?

Rozpakowywanie z jawną siłą

Zasadniczo nigdy nie należy jawnie wymuszać rozpakowywania opcjonalnego z !operatorem. Mogą wystąpić przypadki użycia! jest dopuszczalne - ale powinieneś go zawsze używać, jeśli masz 100% pewności, że opcja zawiera wartość.

Chociaż może się zdarzyć, że możesz użyć wymuszania rozpakowywania, ponieważ wiadomo , że opcja zawiera wartość - nie ma jednego miejsca, w którym nie można bezpiecznie rozpakować tej opcji.

Opcjonalnie nieopakowane opcje

Zmienne te zostały zaprojektowane tak, aby można było odłożyć ich przypisanie na później w kodzie. To jest twój obowiązek upewnić się, że posiadają wartość, zanim do nich dostęp. Ponieważ jednak wiążą się z rozpakowywaniem siły, nadal są z natury niebezpieczne - jak zakładają twoja wartość jest różna od zera, mimo że przypisanie wartości zero jest prawidłowe.

Powinieneś używać domyślnie nieopakowanych opcji jako ostateczności . Jeśli możesz użyć leniwej zmiennej lub podać wartość domyślną dla zmiennej - powinieneś to zrobić zamiast używać opcjonalnie nieopakowanego opcjonalnego.

Istnieje jednak kilka scenariuszy, w których niejawnie nieopakowane opcje są korzystne i nadal możesz korzystać z różnych sposobów bezpiecznego ich rozpakowywania, jak podano poniżej - ale zawsze powinieneś używać ich z należytą ostrożnością.


Jak mogę bezpiecznie radzić sobie z Opcjonalnymi?

Najprostszym sposobem sprawdzenia, czy opcja zawiera wartość, jest jej porównanie 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 opcjonalnymi 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 wiązania .

Opcjonalne wiązanie

Opcjonalne wiązanie pozwala sprawdzić, czy opcjonalne zawiera wartość - i umożliwia przypisanie nieopakowanej wartości do nowej zmiennej lub stałej. Korzysta ze składni if let x = anOptional {...}lub if var x = anOptional {...}, w zależności od tego, czy trzeba 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")
}

Powoduje to najpierw sprawdzenie, 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, jak można się spodziewać.

Zaletą opcjonalnego wiązania jest to, że możesz rozpakować wiele opcji jednocześnie. Możesz po prostu oddzielić instrukcje przecinkiem. Instrukcja powiedzie się, jeśli wszystkie opcje będą 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")
}

Kolejną ciekawą sztuczką jest to, że po rozpakowaniu można 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 błędem przy użyciu opcjonalnego wiązania w instrukcji if jest to, że można uzyskać dostęp do nieopakowanej wartości tylko z zakresu instrukcji. Jeśli potrzebujesz dostępu do wartości spoza zakresu instrukcji, możesz użyć instrukcji wartownika .

Oświadczenie osłona pozwala zdefiniować warunek sukcesu - a obecny zakres będzie tylko kontynuować wykonywanie jeśli warunek jest spełniony. Są one zdefiniowane za pomocą składni guard condition else {...}.

Aby użyć ich z opcjonalnym wiązaniem, możesz to zrobić:

guard let number = anOptionalInt else {
    return
}

(Należy zauważyć, że w ciele strażnika należy użyć jednej z instrukcji transferu sterowania , aby wyjść z zakresu aktualnie wykonywanego kodu).

Jeśli anOptionalIntzawiera wartość, zostanie ona rozpakowana i przypisana do nowej numberstałej. Kod po strażniku będzie kontynuował wykonywanie. Jeśli nie zawiera wartości - wartownik wykona kod w nawiasach, co doprowadzi do przekazania kontroli, dzięki czemu kod natychmiast po nim nie zostanie wykonany.

Naprawdę fajną rzeczą w instrukcjach wartowniczych jest to, że nieopakowana 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 doskonały sposób na wyeliminowanie „piramid zagłady” utworzonych przez zagnieżdżenie 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ługuje instrukcja if, takie jak rozpakowywanie wielu opcji jednocześnie i korzystanie z whereklauzuli.

To, czy użyjesz instrukcji if czy guard, zależy całkowicie od tego, czy jakikolwiek przyszły kod wymaga opcjonalnego zawarcia wartości.

Brak koalescencji operatora

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 nie, to wróć b”. Na przykład możesz użyć tego w następujący sposób:

let number = anOptionalInt ?? 0

Spowoduje to zdefiniowanie numberstałej Inttypu, która będzie zawierać wartość anOptionalInt, jeśli zawiera wartość, lub w 0inny sposób.

To tylko skrót dla:

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

Opcjonalne łączenie

Możesz użyć opcjonalnego łączenia w celu wywołania metody lub uzyskania dostępu do właściwości na opcjonalnym. Dokonuje się tego po prostu przez dodanie nazwy zmiennej za ?pomocą a.

Załóżmy na przykład, że mamy zmienną footypu opcjonalnego Foowystąpienia.

var foo : Foo?

Jeśli chcemy wywołać metodę foo, która niczego nie zwraca, możemy po prostu zrobić:

foo?.doSomethingInteresting()

Jeśli foozawiera wartość, zostanie na niej wywołana ta metoda. Jeśli tak się nie stanie, nic złego się nie stanie - kod będzie po prostu kontynuował wykonywanie.

(Jest to podobne zachowanie jak wysyłanie wiadomości do nilcelu C)

Można to zatem wykorzystać również do ustawiania właściwości, a także metod wywoływania. Na przykład:

foo?.bar = Bar()

Znów nic złego się tu nie stanie, jeśli foojest nil. Twój kod będzie po prostu kontynuował wykonywanie.

Inną ciekawą sztuczką, którą umożliwia opcjonalne łączenie łańcuchów, jest sprawdzenie, czy ustawienie właściwości lub wywołanie metody zakończyło się powodzeniem. Możesz to zrobić, porównując wartość zwracaną z nil.

(Jest tak, ponieważ zwracana jest wartość opcjonalna Void?zamiast Voidmetody, która niczego nie zwraca)

Na przykład:

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

Sprawy stają się jednak nieco trudniejsze przy próbie uzyskania dostępu do właściwości lub wywołania metod zwracających wartość. Ponieważ foojest to opcjonalne, wszystko, co zostanie z niego zwrócone, będzie również opcjonalne. Aby sobie z tym poradzić, możesz rozpakować opcje, które są zwracane za pomocą jednej z powyższych metod - lub rozpakować foosię przed uzyskaniem dostępu do metod lub wywołaniem metod, które zwracają wartości.

Ponadto, jak sugeruje nazwa, można „połączyć” te instrukcje razem. Oznacza to, że jeśli fooma opcjonalną właściwość baz, która ma właściwość qux- możesz napisać następujące:

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 niewykorzystywaną funkcją w opcjach jest możliwość korzystania z funkcji mapi flatMap. Pozwalają one zastosować nie opcjonalne przekształcenia do zmiennych opcjonalnych. Jeśli opcja ma wartość, możesz zastosować do niej daną transformację. Jeśli nie ma wartości, pozostanie nil.

Załóżmy na przykład, że masz opcjonalny ciąg:

let anOptionalString:String?

Stosując mapdo niej funkcję - możemy użyć stringByAppendingStringfunkcji, aby połączyć ją z innym ciągiem.

Ponieważ stringByAppendingStringpobiera nie opcjonalny ciąg znaków, nie możemy bezpośrednio wprowadzić naszego opcjonalnego ciągu. Korzystając z niej map, możemy jednak użyć parametru allow, stringByAppendingStringjeśli anOptionalStringma wartość.

Na przykład:

var anOptionalString:String? = "bar"

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

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

Jednak jeśli 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, ale umożliwia zwrócenie kolejnej opcjonalnej z wnętrza korpusu zamknięcia. Oznacza to, że możesz wprowadzić opcjonalny element w procesie, który wymaga nie opcjonalnego wejścia, ale sam może wypisać 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, błąd zostanie bezpiecznie złapany w catchbloku.

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

Możesz również zadeklarować errorsiebie, ma tę zaletę, że można go rzutować na przydatny format, na przykład:

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

Korzystanie z trytej metody jest właściwym sposobem próbowania, wychwytywania i obsługi błędów pochodzących z funkcji rzucania.

Istnieje również, try?który 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 zapewnia również „wymuszenie próby” za pomocą try!:

let result = try! someThrowingFunc()

Obowiązują tu również pojęcia wyjaśnione w tym poście: 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 bardzo rzadko.

Przez większość czasu będziesz korzystać z pełnego systemu Do-Try-Catch - i opcjonalnego try?, w rzadkich przypadkach, w których obsługa błędu nie jest ważna.


Zasoby

Hamish
źródło
87
Osobiście chciałbym podziękować za włożenie wysiłku w napisanie tego wszystkiego. Wydaje mi się, że z pewnością przyda się to zarówno początkującym, jak i ekspertom korzystającym z szybkiego.
Pranav Wadhwa
15
Cieszę się, że okaże się to pomocne. Ta odpowiedź to wiki społeczności, więc jest to współpraca wielu osób (jak dotąd 7)!
jtbandes
1
Ten błąd występuje w moim przypadku sporadycznie. Jakieś sugestie? Kod w stackoverflow.com/questions/50933681/…
py_ios_dev
1
Być może nadszedł czas, aby zaktualizować tę odpowiedź, aby użyć compactMap()zamiast flatMap().
Nicolas Miari,
więc wydaje mi się, że najlepszym i krótkim rozwiązaniem jest „zerowy operator koalescencyjny”, prawda?
mehdigriche,
65

Odpowiedź TL; DR

Z nielicznymi wyjątkami ta zasada jest złota:

Unikaj używania !

Deklaracja zmiennej opcjonalnej ( ?), niejawnie nieopakowanej opcji (IUO) ( !)

Innymi słowy, użyj raczej:
var nameOfDaughter: String?

Zamiast:
var nameOfDaughter: String!

Rozpakuj opcjonalną zmienną 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

Sajjon
źródło
40

To pytanie pojawia się CAŁY CZAS na SO. Jest to jedna z pierwszych rzeczy, z którymi zmagają się nowi programiści Swift.

Tło:

Swift używa pojęcia „Opcjonalne” do radzenia 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. Co jednak, jeśli 0 jest prawidłową wartością? Wtedy możesz użyć -1. Co jeśli -1 jest prawidłową wartością? I tak dalej.

Opcjonalne opcje szybkie pozwalają skonfigurować zmienną dowolnego typu, tak aby zawierała albo prawidłową wartość, albo żadnej wartości.

Umieszczasz znak zapytania za typem, kiedy deklarujesz zmienną jako średnią (wpisz x lub brak wartości).

Opcjonalny to tak naprawdę kontener, który zawiera albo zmienną danego typu, albo nic.

Opcjonalne musi być „rozpakowane”, aby pobrać wartość w środku.

„!” operator jest operatorem „wymuszającego rozpakowywanie”. Mówi: „zaufaj mi. Wiem, co robię. Gwarantuję, że kiedy ten kod się uruchomi, zmienna nie będzie zawierać zero”. Jeśli się mylisz, upaść.

Chyba, że naprawdę nie wiem, co robisz, uniknąć „!” wymuś operator odwijania. 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 opcjami, które są bezpieczniejsze. Oto niektóre (nie wyczerpująca lista)

Możesz użyć „opcjonalnego wiązania” lub „jeśli pozwala”, aby powiedzieć „jeśli ta opcjonalna zawiera wartość, zapisz tę wartość w nowej, nie opcjonalnej zmiennej. Jeśli opcjonalna nie zawiera wartości, pomiń treść 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")
}

Zauważ, że zmienna, którą definiujesz, kiedy używasz opcjonalnej oferty, istnieje tylko (jest tylko „w zakresie”) w treści instrukcji if.

Alternatywnie możesz użyć instrukcji guard, która pozwala wyjść z funkcji, jeśli zmienna ma wartość zero:

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

Instrukcje Swift zostały dodane w Swift 2. Guard pozwala zachować „złotą ścieżkę” w kodzie i uniknąć ciągle rosnących poziomów zagnieżdżonych, jeśli czasami wynikają z użycia opcjonalnego wiązania „jeśli pozwól”.

Istnieje również konstrukcja zwana „zerowym operatorem koalescencyjnym”. Przybiera postać „opcjonalny_war ?? wartość_wymienności”. Zwraca nie opcjonalną zmienną tego samego typu, co dane zawarte w opcjonalnej. Jeśli opcjonalny zawiera zero, zwraca wartość wyrażenia po „??” 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 użyć obsługi błędów try / catch lub guard, ale ogólnie jedna z powyższych technik jest czystsza.

EDYTOWAĆ:

Inną, nieco bardziej subtelną gotcha z opcjami jest „domyślnie nieopakowane opcje. Kiedy deklarujemy foo, możemy powiedzieć:

var foo: String!

W takim przypadku foo nadal jest opcjonalne, ale nie trzeba go rozpakowywać, aby się do niego odwoływać. Oznacza to, że za każdym razem, gdy spróbujesz odwołać się do foo, nastąpi awaria, jeśli jest ona zerowa.

Więc ten kod:

var foo: String!


let upperFoo = foo.capitalizedString

Zawiesza się w związku z właściwością capitalizedString foo, nawet jeśli nie jesteśmy foo rozpakowywaniem wymuszonym. 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 zrozumiesz dobrze opcji).

Konkluzja: Kiedy uczysz się Swift po raz pierwszy, udawaj „!” znak nie jest częścią języka. To może wpędzić cię w kłopoty.

Duncan C.
źródło
7
Powinieneś rozważyć utworzenie publicznej wiki.
vacawama
3
Edytuj swoją odpowiedź, a pod polem edycji po prawej stronie znajduje się pole wyboru wiki społeczności . Nie będziesz już otrzymywać odpowiedzi za odpowiedź, ale wiem, że i tak nie jest to rażący chwyt.
vacawama
6
Biorąc pod uwagę, że to pytanie jest zadawane milion razy na stronie, jeśli miałbym poświęcić czas na opublikowanie pytania z odpowiedzią na siebie jako kanonicznej odpowiedzi na pytanie, mam nadzieję, że odpowiedź jest o wiele bardziej kompletna. W guardklauzulach nie ma nic . Nie ma nic w użyciu, if varktórego konstrukcja jest tak samo ważna jak konstrukcja if let. W whereklauzulach nie ma nic wartego wspomnienia, gdy mówimy o if letwiązaniu (w wielu przypadkach usuwa całą warstwę zagnieżdżania). W opcjonalnym łańcuchowaniu nie ma nic.
nhgrif
6
A kiedy wspominasz o niejawnie nieopakowanych opcjach, nie wspominasz nawet, że możesz użyć wszystkich wyżej wymienionych opcjonalnych sztuczek, aby bezpiecznie rozpakować niejawnie nieopakowane opcje. Nie obejmujemy również rozpakowywania wielu opcji w tej samej klauzuli.
nhgrif
1
@nhgrif, powiedziałem: „Możesz także użyć obsługi błędów try / catch lub guard, ale ogólnie jedna z powyższych technik jest czystsza”. Z pewnością masz kilka dobrych punktów. To dość duży temat. Co powiesz na udzielenie własnej odpowiedzi, która obejmuje rzeczy, których nie wolałem, niż komentowanie? W ten sposób pytanie i odpowiedzi stają się lepszym atutem witryny.
Duncan C
13

Ponieważ powyższe odpowiedzi jasno wyjaśniają, jak bezpiecznie grać z Opcjonalnymi. Spróbuję wyjaśnić, jakie Opcjonalnie są naprawdę szybkie.

Innym sposobem zadeklarowania zmiennej opcjonalnej jest

var i : Optional<Int>

A typ opcjonalny to nic innego jak wyliczenie z dwoma przypadkami, tj

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

Aby więc przypisać zero do naszej zmiennej „i”. Możemy zrobić var i = Optional<Int>.none lub przypisać wartość, przekażemy pewną wartość var i = Optional<Int>.some(28)

Według szybkiego „zero” oznacza brak wartości. I utworzyć instancję zainicjowany ze nilMusimy być zgodne z protokołem o nazwie ExpressibleByNilLiterali wielkiej jeśli zgadłeś, tylko Optionalszgodne ExpressibleByNilLiterali zgodne z innymi typami nie jest zalecane.

ExpressibleByNilLiteralma jedną wywoływaną metodę, init(nilLiteral:)która inicjuje instancję zerem. Zwykle nie wywołujesz tej metody i zgodnie z szybką dokumentacją odradza się wywoływanie tego inicjalizatora bezpośrednio, ponieważ kompilator wywołuje go za każdym razem, gdy inicjujesz typ opcjonalny za pomocą nilliterału.

Nawet ja muszę owinąć (bez słów) moją głowę wokół Opcjonalnie: D Happy Swfting All .

Tharzeez
źródło
12

Po pierwsze, powinieneś wiedzieć, co to jest wartość opcjonalna. Aby uzyskać szczegółowe informacje, możesz przejść do języka programowania Swift .

Po drugie, powinieneś wiedzieć, że opcjonalna wartość ma dwa statusy. Jedna to pełna wartość, a druga zero. Dlatego przed wdrożeniem wartości opcjonalnej powinieneś sprawdzić, który to stan.

Można użyć if let ...albo guard let ... elsei tak dalej.

Innym sposobem jest to, że jeśli nie chcesz sprawdzać stanu zmiennej przed implementacją, możesz również użyć var buildingName = buildingName ?? "buildingName".

QiunCheng
źródło
8

Miałem ten błąd raz, gdy próbowałem ustawić moje wartości gniazd w metodzie 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
        }
    }
}

Potem dowiedziałem się, że nie mogę ustawić wartości gniazd 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, kto ma ten sam problem, ponieważ uznałem, że zaznaczona odpowiedź jest doskonałym źródłem wiedzy na temat opcji i ich działania, ale nie rozwiązała bezpośrednio tego problemu.

Wissa
źródło
Tylko nie zapomnij wspomnieć, gdzie zadzwonić updateVieww kontrolerze docelowym;)
Ahmad F
@AhmadF dobry punkt. Jednak wywołanie updateViewkontrolera docelowego nie jest w tym przypadku potrzebne, ponieważ używam namezmiennej, aby ustawić nameLabel.textw viewDidLoad. Ale gdybyśmy przeprowadzali wiele instalacji, z pewnością lepiej byłoby stworzyć inną funkcję, wykonując to i wywołując ją viewDidLoadzamiast tego.
Wissa
7

Zasadniczo próbowałeś użyć wartości zerowej w miejscach, w których Swift zezwala tylko na wartości inne niż zero, mówiąc kompilatorowi, aby ci ufał, że nigdy nie będzie żadnej wartości zerowej, umożliwiając w ten sposób kompilacji aplikacji.

Istnieje kilka scenariuszy, które prowadzą do tego rodzaju krytycznego błędu:

  1. wymuszone rozpakowywanie:

    let user = someVariable!

    Jeśli someVariablejest zero, nastąpi awaria. Wykonując wymuszone rozpakowywanie, przeniosłeś na siebie odpowiedzialność za sprawdzenie zerowe z kompilatora, w zasadzie poprzez wymuszone rozpakowywanie gwarantujesz kompilatorowi, że nigdy nie będziesz mieć żadnych wartości zerowych. I zgadnij, co się stanie, jeśli jakoś zero wyniesie someVariable?

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

    if user = someVariable {
        // do your stuff
    }
  2. wymuszone (w dół) rzuty:

    let myRectangle = someShape as! Rectangle

    Tutaj przez rzucanie siłą mówisz kompilatorowi, żeby się nie martwił, ponieważ zawsze będziesz miał tam Rectangleinstancję. I dopóki to trwa, nie musisz się martwić. Problemy zaczynają się, gdy ty lub twoi koledzy z projektu zaczynacie krążyć wartości inne niż prostokąt.

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

    if let myRectangle = someShape as? Rectangle {
        // yay, I have a rectangle
    }
  3. Niejawnie nieopakowane 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 tą namewłaściwością, ustawiając ją na nil, to działa zgodnie z oczekiwaniami, jednak jeśli Userjest inicjowany z JSON, który nie ma nameklucza, wówczas występuje błąd krytyczny przy próbie użycia właściwości.

    Rozwiązanie? Nie używaj ich :) O ile nie masz 102% pewności, że właściwość zawsze będzie miała wartość inną niż zero, zanim będzie potrzebna. W większości przypadków konwersja na opcjonalną lub nie opcjonalną będzie działać. Jeśli nie będzie opcjonalny, kompilator pomoże ci również powiedzieć ścieżki kodu, które przegapiłeś, podając wartość tej właściwości

  4. Odłączone lub jeszcze nie podłączone gniazda. Jest to szczególny przypadek scenariusza nr 3. Zasadniczo masz klasę zał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ł skorzystać z gniazdka. Rozwiązanie? Upewnij się, że wszystkie gniazda są podłączone. Lub użyj ?operatora na nich emailTextField?.text = "[email protected]". Lub zadeklaruj gniazdko jako opcjonalne, ale w tym przypadku kompilator zmusi cię do rozpakowania całego kodu.

  5. Wartości pochodzące z Celu-C, które nie zawierają adnotacji o zerowaniu. Załóżmy, że mamy następującą klasę Objective-C:

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

    Teraz, jeśli nie określono adnotacji o zerowaniu (jawnie lub za pomocą NS_ASSUME_NONNULL_BEGIN/ NS_ASSUME_NONNULL_END), namewłaściwość zostanie zaimportowana do Swift jako String!(opcjonalnie IUO - niejawnie rozpakowane). Gdy tylko jakiś szybki kod będzie chciał użyć tej wartości, zawiesi się, jeśli namebędzie zero.

    Rozwiązanie? Dodaj adnotacje nullability do kodu Objective-C. Uważaj jednak, kompilator Objective-C jest trochę liberalny, jeśli chodzi o zerowanie, możesz skończyć z zerowymi wartościami, nawet jeśli wyraźnie je oznaczysz jako nonnull.

Cristik
źródło
2

Jest to ważniejszy komentarz i dlatego niejawnie nieopakowane opcje mogą być zwodnicze, 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 obiekt jest nil?

Nie możesz!

Pełny kod wyglądałby następująco:

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ę zawiesi, jesteś jak „co do diabła ?! mój addressnie jest opcjonalny, więc dlaczego się rozbiję ?!

Dlatego lepiej jest pisać jako taki:

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 został ci wyjaśniony. Możesz zracjonalizować i pomyśleć, że prawdopodobnie jest to addressparametr, który został siłą rozpakowany.

Pełny kod wyglądałby następująco:

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
}
Kochanie
źródło
2

Błędy EXC_BAD_INSTRUCTIONi fatal error: unexpectedly found nil while implicitly unwrapping an Optional valuepojawia się najczęściej, gdy zadeklarowałeś scenariusz@IBOutlet , ale nie masz połączenia z nim .

Powinieneś także dowiedzieć się o tym, jak działają Opcjonalne , wspomniane w innych odpowiedziach, ale to jedyny czas, który wydaje mi się najczęściej.

Ale Mohamad
źródło
czy @IBOutletprzyczyną tego błędu nie powinien być błąd krytyczny: nieoczekiwanie znaleziono zero podczas niejawnego rozpakowywania opcjonalnej wersji błędu?
pkamb
1
To prawda. Może, kiedy wysyłam odpowiedź, właśnie to miałem na myśli i skopiowałem wklejony pierwszy krytyczny komunikat o błędzie. Odpowiedź Hamisha wydaje się bardzo kompletna.
Ale Mohamad,
2

Jeśli pojawi się ten błąd w CollectionView, spróbuj również utworzyć plik CustomCell i niestandardowy xib.

dodaj ten kod w ViewDidLoad () na mainVC.

    let nib = UINib(nibName: "CustomnibName", bundle: nil)
    self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")
ShigaSuresh
źródło
0

Ten błąd napotkałem podczas tworzenia segmentu z kontrolera widoku tabeli do kontrolera widoku, ponieważ zapomniałem podać niestandardową nazwę klasy dla kontrolera widoku w głównej scenorysie.

Coś prostego, co warto sprawdzić, czy wszystko inne wygląda dobrze

Mike Volmar
źródło