Opcjonalny w Swift to typ, który może przechowywać wartość lub nie zawierać żadnej wartości. Opcjonalne są zapisywane przez dodanie ?
do dowolnego typu:
var name: String? = "Bertie"
Opcjonalne (wraz z Generics) są jedną z najtrudniejszych do zrozumienia koncepcji Swift. Ze względu na sposób, w jaki są pisane i używane, łatwo jest źle zrozumieć, jakie są. Porównaj opcjonalne powyżej z tworzeniem normalnego ciągu:
var name: String = "Bertie" // No "?" after String
Ze składni wygląda na to, że opcjonalny ciąg znaków jest bardzo podobny do zwykłego ciągu znaków. To nie jest. Opcjonalny ciąg nie jest ciągiem z włączonymi niektórymi ustawieniami „opcjonalnymi”. To nie jest specjalna odmiana Sznurka. Ciąg i opcjonalny ciąg to zupełnie różne typy.
Oto najważniejsza rzecz, którą należy wiedzieć: Opcjonalnie jest rodzajem pojemnika. Opcjonalny ciąg to kontener, który może zawierać ciąg. Opcjonalny Int to kontener, który może zawierać Int. Pomyśl o opcjonalnym jako paczce. Zanim go otworzysz (lub „rozpakujesz” w języku opcjonalnym) nie będziesz wiedział, czy zawiera on coś, czy nic.
Możesz zobaczyć, w jaki sposób opcje są implementowane w Swift Standard Library, wpisując „Opcjonalne” w dowolnym pliku Swift i klikając go ⌘. Oto ważna część definicji:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Opcjonalne jest tylko enum
jedno z dwóch przypadków: .none
lub .some
. Jeśli tak .some
, istnieje powiązana wartość, która w powyższym przykładzie byłaby String
„Cześć”. Opcjonalne używa Rodzajowych, aby nadać typ powiązanej wartości. Typ opcjonalnego ciągu nie jest String
, jest Optional
, a dokładniejOptional<String>
.
Wszystko, co Swift robi z opcjami, to magia, dzięki której czytanie i pisanie kodu jest płynniejsze. Niestety przesłania to sposób, w jaki faktycznie działa. Później przejdę przez niektóre sztuczki.
Uwaga: Będę dużo mówił o zmiennych opcjonalnych, ale dobrze jest również tworzyć opcjonalne stałe. Zaznaczam wszystkie zmienne ich typem, aby ułatwić zrozumienie tworzonych typów typów, ale nie musisz tego robić we własnym kodzie.
Jak tworzyć opcje
Aby utworzyć opcjonalny, dodaj ?
po typ, który chcesz zawinąć. Każdy typ może być opcjonalny, nawet własne typy niestandardowe. Nie możesz mieć spacji między typem a ?
.
var name: String? = "Bob" // Create an optional String that contains "Bob"
var peter: Person? = Person() // An optional "Person" (custom type)
// A class with a String and an optional String property
class Car {
var modelName: String // must exist
var internalName: String? // may or may not exist
}
Korzystanie z opcji
Możesz porównać opcjonalny, nil
aby sprawdzić, czy ma wartość:
var name: String? = "Bob"
name = nil // Set name to nil, the absence of a value
if name != nil {
print("There is a name")
}
if name == nil { // Could also use an "else"
print("Name has no value")
}
To jest trochę mylące. Oznacza to, że opcjonalne to jedno lub drugie. To albo zero, albo „Bob”. To nie jest prawda, opcja nie przekształca się w coś innego. Porównywanie go do zera jest sztuczką, aby uczynić kod łatwiejszym do odczytania. Jeśli opcja jest równa zero, oznacza to po prostu, że wyliczenie jest obecnie ustawione na .none
.
Tylko opcje mogą być zerowe
Jeśli spróbujesz ustawić nie opcjonalną zmienną na zero, pojawi się błąd.
var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'
Innym sposobem patrzenia na opcje jest uzupełnienie normalnych zmiennych Swift. Są odpowiednikiem zmiennej, która ma gwarantowaną wartość. Swift to ostrożny język, który nie znosi dwuznaczności. Większość zmiennych jest definiowanych jako nieopcjonalne, ale czasami nie jest to możliwe. Na przykład wyobraź sobie kontroler widoku, który ładuje obraz z pamięci podręcznej lub z sieci. Może lub nie może mieć tego obrazu w momencie tworzenia kontrolera widoku. Nie ma sposobu, aby zagwarantować wartość zmiennej obrazu. W takim przypadku należy ustawić opcję opcjonalną. Zaczyna się, nil
gdy obraz jest pobierany, opcjonalnie pobiera wartość.
Użycie opcjonalnego ujawnia zamiary programistów. W porównaniu do Celu C, w którym dowolny obiekt może być zerowy, Swift wymaga jasnego określenia, kiedy może brakować wartości i kiedy istnieje gwarancja jej istnienia.
Aby użyć opcjonalnego, „rozpakuj” go
Opcjonalnego String
nie można użyć zamiast faktycznego String
. Aby użyć zawiniętej wartości wewnątrz opcjonalnej, musisz ją rozpakować. Najprostszym sposobem na rozpakowanie opcjonalnego jest dodanie !
po opcjonalnej nazwie. Nazywa się to „wymuszaniem rozpakowywania”. Zwraca wartość wewnątrz opcjonalnego (jako pierwotnego typu), ale jeśli opcjonalny jest nil
, powoduje awarię środowiska wykonawczego. Przed rozpakowaniem należy upewnić się, że istnieje wartość.
var name: String? = "Bob"
let unwrappedName: String = name!
print("Unwrapped name: \(unwrappedName)")
name = nil
let nilName: String = name! // Runtime crash. Unexpected nil.
Sprawdzanie i korzystanie z opcjonalnego
Ponieważ zawsze należy sprawdzić zero przed rozpakowaniem i użyciem opcjonalnego, jest to powszechny wzorzec:
var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
let unwrappedMealPreference: String = mealPreference!
print("Meal: \(unwrappedMealPreference)") // or do something useful
}
W tym wzorze sprawdzasz, czy wartość jest obecna, a następnie, gdy jesteś pewien, że tak jest, wymuszasz rozpakowanie jej do tymczasowej stałej do użycia. Ponieważ jest to tak powszechne, Swift oferuje skrót za pomocą „jeśli pozwala”. Nazywa się to „opcjonalnym wiązaniem”.
var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
print("Meal: \(unwrappedMealPreference)")
}
Tworzy to tymczasową stałą (lub zmienną, jeśli zastąpisz let
ją var
), której zakres znajduje się tylko w nawiasach if. Ponieważ używanie nazwy takiej jak „unwrappedMealPreference” lub „realMealPreference” jest dużym obciążeniem, Swift pozwala na ponowne użycie oryginalnej nazwy zmiennej, tworząc tymczasową w zakresie nawiasów
var mealPreference: String? = "Vegetarian"
if let mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // separate from the other mealPreference
}
Oto kod pokazujący, że używana jest inna zmienna:
var mealPreference: String? = "Vegetarian"
if var mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // mealPreference is a String, not a String?
mealPreference = "Beef" // No effect on original
}
// This is the original mealPreference
print("Meal: \(mealPreference)") // Prints "Meal: Optional("Vegetarian")"
Opcjonalne wiązanie działa poprzez sprawdzenie, czy opcjonalne jest równe zero. Jeśli nie, rozpakowuje opcjonalne do podanej stałej i wykonuje blok. W Xcode 8.3 i późniejszych (Swift 3.1) próba wydrukowania takiej opcji opcjonalnej spowoduje bezużyteczne ostrzeżenie. Użyj opcji opcjonalnych, debugDescription
aby go wyciszyć:
print("\(mealPreference.debugDescription)")
Do czego służą opcje?
Opcjonalne mają dwa przypadki użycia:
- Rzeczy, które mogą zawieść (oczekiwałem czegoś, ale nic nie dostałem)
- Rzeczy, które są niczym, ale mogą być czymś później (i odwrotnie)
Niektóre konkretne przykłady:
- Właściwość, która może być tam lub nie, jak
middleName
lub spouse
wPerson
klasie
- Metoda, która może zwrócić wartość lub nic, na przykład wyszukiwanie dopasowania w tablicy
- Metoda, która może zwrócić wynik lub otrzymać błąd i nic nie zwrócić, na przykład próbę odczytania zawartości pliku (która zwykle zwraca dane pliku), ale plik nie istnieje
- Deleguj właściwości, które nie zawsze muszą być ustawiane i generalnie są ustawiane po inicjalizacji
- Dla
weak
właściwości w klasach. To, na co wskazują, można ustawić nil
w dowolnym momencie
- Duży zasób, który może wymagać zwolnienia w celu odzyskania pamięci
- Gdy potrzebujesz sposobu, aby dowiedzieć się, kiedy wartość została ustawiona (dane jeszcze nie załadowane> dane) zamiast korzystać z osobnych danych
Boolean
Opcje nie istnieją w Objective-C, ale istnieje równoważna koncepcja, zwracająca zero. Metody, które mogą zwrócić obiekt, mogą zamiast tego zwrócić zero. Ma to oznaczać „brak ważnego obiektu” i często używane jest do powiedzenia, że coś poszło nie tak. Działa tylko z obiektami Objective-C, a nie z prymitywami lub podstawowymi typami C (wyliczenia, struktury). Cel C często nie wyspecjalizowane typy reprezentuje brak tych wartości ( NSNotFound
która jest naprawdę NSIntegerMax
, kCLLocationCoordinate2DInvalid
reprezentuje nieprawidłową współrzędnych -1
lub jakąś wartość ujemna są używane). Koder musi wiedzieć o tych specjalnych wartościach, dlatego należy je udokumentować i nauczyć się dla każdego przypadku. Jeśli metoda nie mogła przyjąć,nil
parametru, należy to udokumentować. W celu Cnil
był wskaźnikiem, tak jak wszystkie obiekty zostały zdefiniowane jako wskaźniki, ale nil
wskazywały na określony (zerowy) adres. W Swift nil
jest dosłowny, co oznacza brak określonego typu.
W porównaniu do nil
Kiedyś byłeś w stanie użyć dowolnej opcjonalnej jako Boolean
:
let leatherTrim: CarExtras? = nil
if leatherTrim {
price = price + 1000
}
W nowszych wersjach Swift musisz używać leatherTrim != nil
. Dlaczego to? Problem polega na tym, że Boolean
można zawinąć w opcjonalny. Jeśli Boolean
tak, to:
var ambiguous: Boolean? = false
ma dwa rodzaje „fałszu”, jeden, w którym nie ma wartości, i jeden, w którym ma wartość, ale wartość jest false
. Szybka nienawidzi dwuznaczności, więc teraz zawsze musisz sprawdzić opcję przeciw nil
.
Zastanawiasz się, jaki jest sens opcjonalności Boolean
? Podobnie jak w przypadku innych opcji, .none
stan może wskazywać, że wartość jest jak dotąd nieznana. Na drugim końcu połączenia sieciowego może znajdować się coś, co zajmuje trochę czasu, aby sondować. Opcjonalne booleany nazywane są również „ booleanami trzech wartości ”
Szybkie sztuczki
Swift wykorzystuje kilka sztuczek, aby umożliwić działanie opcji. Rozważ te trzy linie zwykłego kodu opcjonalnego;
var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }
Żadna z tych linii nie powinna się skompilować.
- Pierwszy wiersz ustawia opcjonalny ciąg znaków za pomocą literału ciągu znaków, dwa różne typy. Nawet jeśli był to
String
typ, są różne
- Druga linia ustawia opcjonalny Ciąg na zero, dwa różne typy
- Trzeci wiersz porównuje opcjonalny ciąg do zera, dwa różne typy
Omówię niektóre szczegóły implementacji opcji, które pozwalają na działanie tych linii.
Tworzenie opcjonalnego
Używanie ?
do tworzenia opcjonalnego jest cukrem syntaktycznym, włączanym przez kompilator Swift. Jeśli chcesz to zrobić na dłuższą metę, możesz utworzyć opcjonalne takie jak to:
var name: Optional<String> = Optional("Bob")
Wywołuje Optional
to pierwszy inicjalizator, public init(_ some: Wrapped)
który wyprowadza typ opcjonalny z typu używanego w nawiasach.
Jeszcze dłuższy sposób tworzenia i ustawiania opcjonalnego:
var serialNumber:String? = Optional.none
serialNumber = Optional.some("1234")
print("\(serialNumber.debugDescription)")
Ustawienie opcjonalne na nil
Możesz utworzyć opcjonalny bez wartości początkowej lub utworzyć wartość początkową nil
(oba mają ten sam wynik).
var name: String?
var name: String? = nil
Umożliwienie równości opcji nil
jest możliwe dzięki protokołowi ExpressibleByNilLiteral
(poprzednio nazwanemu NilLiteralConvertible
). Opcjonalny jest tworzony z Optional
drugim co inicjatora, public init(nilLiteral: ())
. Dokumenty mówią, że nie powinieneś używać ExpressibleByNilLiteral
do niczego oprócz opcji, ponieważ zmieniłoby to znaczenie zero w twoim kodzie, ale można to zrobić:
class Clint: ExpressibleByNilLiteral {
var name: String?
required init(nilLiteral: ()) {
name = "The Man with No Name"
}
}
let clint: Clint = nil // Would normally give an error
print("\(clint.name)")
Ten sam protokół umożliwia ustawienie już utworzonej opcji na nil
. Chociaż nie jest to zalecane, możesz użyć inicjatora zerowego dosłowności bezpośrednio:
var name: Optional<String> = Optional(nilLiteral: ())
Porównanie opcjonalnego z nil
Opcje definiują dwa specjalne operatory „==” i „! =”, Które można zobaczyć w Optional
definicji. Pierwszy ==
pozwala sprawdzić, czy jakakolwiek opcja jest równa zero. Dwie różne opcje, które są ustawione na .none, zawsze będą równe, jeśli powiązane typy są takie same. Kiedy porównujesz do zera, za kulisami Swift tworzy opcjonalny tego samego powiązanego typu, ustaw na .none, a następnie używa go do porównania.
// How Swift actually compares to nil
var tuxedoRequired: String? = nil
let temp: Optional<String> = Optional.none
if tuxedoRequired == temp { // equivalent to if tuxedoRequired == nil
print("tuxedoRequired is nil")
}
Drugi ==
operator umożliwia porównanie dwóch opcji. Oba muszą być tego samego typu i ten typ musi być zgodny Equatable
(protokół, który pozwala na porównanie rzeczy ze zwykłym operatorem „==”). Szybki (przypuszczalnie) rozpakowuje dwie wartości i porównuje je bezpośrednio. Obsługuje również przypadek, w którym znajduje się jedna lub obie opcje .none
. Zwróć uwagę na różnicę między porównywaniem z nil
literałem.
Ponadto pozwala porównać dowolny Equatable
typ z opcjonalnym opakowaniem tego typu:
let numberToFind: Int = 23
let numberFromString: Int? = Int("23") // Optional(23)
if numberToFind == numberFromString {
print("It's a match!") // Prints "It's a match!"
}
Za kulisami Swift otacza nieopcjonalne jako opcjonalne przed porównaniem. Działa również z literałami ( if 23 == numberFromString {
)
Powiedziałem, że są dwa ==
operatory, ale tak naprawdę jest trzeci, który pozwala umieścić nil
po lewej stronie porównania
if nil == name { ... }
Opcje nazewnictwa
Nie ma konwencji Swift dotyczącej nazewnictwa typów opcjonalnych inaczej niż typów nieobowiązkowych. Ludzie unikają dodawania czegoś do nazwy, aby pokazać, że jest ona opcjonalna (np. „OpcjonalnaŚredniaNazwa” lub „możliwaNumerAsString”) i pozwalają deklaracji pokazać, że jest to typ opcjonalny. Staje się to trudne, gdy chcesz nazwać coś, co będzie zawierało wartość opcjonalną. Nazwa „middleName” implikuje, że jest to typ String, więc kiedy wyodrębnisz z niej wartość String, często możesz skończyć z nazwami takimi jak „actualMiddleName” lub „unwrappedMiddleName” lub „realMiddleName”. Użyj opcjonalnego wiązania i ponownie użyj nazwy zmiennej, aby obejść ten problem.
Oficjalna definicja
Z „Podstawy” w Swift Programming Language :
Swift wprowadza również typy opcjonalne, które obsługują brak wartości. Opcjonalne mówią albo: „istnieje wartość i jest ona równa x” lub „w ogóle nie ma wartości”. Opcjonalne są podobne do używania zer ze wskaźnikami w Objective-C, ale działają dla każdego typu, nie tylko klas. Opcje są bezpieczniejsze i bardziej wyraziste niż wskaźniki zero w Objective-C i są sercem wielu najpotężniejszych funkcji Swift.
Opcjonalne są przykładem tego, że Swift jest bezpiecznym językiem. Swift pomaga jasno określić typy 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. Umożliwia to wychwycenie i naprawienie błędów na jak najwcześniejszym etapie procesu programowania.
Na koniec oto wiersz z 1899 roku na temat opcji:
Wczoraj na schodach
spotkałem mężczyznę, którego tam nie było.
Nie było go dzisiaj.
Chciałbym, chciałbym, żeby odszedł
Antigonish
Więcej zasobów: