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 Optional
jest 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ę -1
go używać do wskazywania braku liczby całkowitej, a może INT_MIN
innej 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 nil
nie jest to samo, co nil
w Objective-C. W Objective-C nil
oznacza 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.
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 anOptionalString
jestnil
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ą nil
w 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 anOptionalInt
zawiera wartość, zostanie ona rozpakowana i przypisana do nowej number
stał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 where
klauzuli.
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 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 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 number
stałej Int
typu, która będzie zawierać wartość anOptionalInt
, jeśli zawiera wartość, lub w 0
inny 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ą foo
typu opcjonalnego Foo
wystą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 foo
zawiera 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 nil
celu 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 foo
jest 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 Void
metody, 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ż foo
jest 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ć foo
się 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 foo
ma 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ż 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 niewykorzystywaną funkcją w opcjach jest możliwość korzystania z funkcji map
i 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 map
do niej funkcję - możemy użyć stringByAppendingString
funkcji, aby połączyć ją z innym ciągiem.
Ponieważ stringByAppendingString
pobiera 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, stringByAppendingString
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")
Jednak jeśli 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
, 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 catch
bloku.
error
Stały widać w catch
bloku nie zostało uznane przez nas - to automatycznie generowane przez catch
.
Możesz również zadeklarować error
siebie, 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 try
tej 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