Co to znaczy „rozpakować instancję”? Dlaczego jest to konieczne?
O ile mogę to wypracować (to też jest dla mnie bardzo nowe) ...
Termin „zawinięty” sugeruje, że powinniśmy myśleć o zmiennej opcjonalnej jako prezentu, owiniętego w błyszczący papier, który może (niestety!) Być pusty .
Po „zawinięciu” wartością zmiennej opcjonalnej jest wyliczenie z dwiema możliwymi wartościami (trochę jak wartość logiczna). To wyliczenie opisuje, czy zmienna zawiera wartość ( Some(T)
), czy nie ( None
).
Jeśli istnieje wartość, można ją uzyskać poprzez „rozpakowanie” zmiennej (uzyskanie T
z Some(T)
).
Czym się john!.apartment = number73
różni john.apartment = number73
? (Parafraza)
Jeśli napiszesz nazwę zmiennej opcjonalnej (np. Tekst john
, bez !
), odnosi się to do enum „zawiniętego” (Some / None), a nie do samej wartości (T). Więc john
to nie jest przypadek Person
i nie ma apartment
członka:
john.apartment
// 'Person?' does not have a member named 'apartment'
Rzeczywistą Person
wartość można rozpakować na różne sposoby:
- „wymuszone rozpakowywanie”:
john!
(podaje Person
wartość, jeśli istnieje, błąd czasu wykonywania, jeśli jest zerowy)
- „opcjonalne wiązanie”:
if let p = john { println(p) }
(wykonuje, println
jeśli wartość istnieje)
- „opcjonalne łączenie”:
john?.learnAboutSwift()
(wykonuje tę wymyśloną metodę, jeśli wartość istnieje)
Myślę, że wybrałeś jeden z tych sposobów na rozpakowanie, w zależności od tego, co powinno się zdarzyć w przypadku zerowym i od tego, jak prawdopodobne jest to. Ten projekt języka wymusza jawne traktowanie przypadku zerowego, co, jak sądzę, poprawia bezpieczeństwo w stosunku do Obj-C (gdzie łatwo jest zapomnieć o obsłudze zerowego przypadku).
Aktualizacja :
Wykrzyknik jest również używany w składni do deklarowania „niejawnie nieopakowanych opcji”.
W dotychczasowych przykładach john
zmienna została zadeklarowana jako var john:Person?
i jest Opcjonalna. Jeśli chcesz rzeczywistą wartość tej zmiennej, musisz ją rozpakować, używając jednej z trzech powyższych metod.
Gdyby var john:Person!
zamiast tego został zadeklarowany , zmienna byłaby opcjonalnie nieopakowana (patrz sekcja z tym nagłówkiem w książce Apple). Nie ma potrzeby rozpakowywania tego rodzaju zmiennej podczas uzyskiwania dostępu do wartości i john
można jej używać bez dodatkowej składni. Ale książka Apple mówi:
Niejawnie nieopakowane opcje nie powinny być używane, gdy istnieje możliwość, że zmienna stanie się zerowa w późniejszym momencie. Zawsze używaj normalnego typu opcjonalnego, jeśli chcesz sprawdzić zerową wartość w okresie istnienia zmiennej.
Aktualizacja 2 :
Artykuł „ Ciekawe funkcje szybkie ” autorstwa Mike'a Ash'a stanowi motywację dla opcjonalnych typów. Myślę, że to świetne, jasne pisanie.
Aktualizacja 3 :
Kolejny przydatny artykuł na temat domyślnie nieopakowanego opcjonalnego użycia wykrzyknika: „ Swift and the Last Mile ” autorstwa Chrisa Adamsona. W artykule wyjaśniono, że jest to pragmatyczna miara zastosowana przez Apple do zadeklarowania typów używanych przez ich frameworki Objective-C, które mogą zawierać zero. Uznanie typu za opcjonalny (za pomocą ?
) lub niejawnie rozpakowany (za pomocą !
) jest „kompromisem między bezpieczeństwem a wygodą”. W przykładach podanych w tym artykule Apple zdecydowało się zadeklarować typy jako niejawnie rozpakowane, dzięki czemu kod wywoływania jest wygodniejszy, ale mniej bezpieczny.
Być może Apple może przeczesywać swoje frameworki w przyszłości, usuwając niepewność domyślnie nieopakowanych („prawdopodobnie nigdy zero”) parametrów i zastępując je opcjonalnymi („na pewno może być to zero w szczególności [mam nadzieję, udokumentowane!] Okoliczności”) lub standardowe -optional („nigdy nie jest zero”) deklaracje, oparte na dokładnym zachowaniu ich kodu Objective-C.
Oto, jak myślę, różnica:
Oznacza, że John może być zerowy
Kompilator zinterpretuje ten wiersz jako:
Podczas
Kompilator zinterpretuje tę linię jako po prostu:
Dlatego za pomocą! rozpakuje instrukcję if i sprawi, że będzie działać szybciej, ale jeśli John ma zero, to wystąpi błąd czasu wykonywania.
Więc zawijanie tutaj nie oznacza, że jest opakowane w pamięć, ale oznacza, że jest opakowane w kod, w tym przypadku jest owinięte instrukcją if, a ponieważ Apple zwraca szczególną uwagę na wydajność w czasie wykonywania, chcą dać ci sposób na spraw, by Twoja aplikacja działała z najlepszą możliwą wydajnością.
Aktualizacja:
Wracając do tej odpowiedzi po 4 latach, ponieważ uzyskałem z niej najwyższą reputację w Stackoverflow :) Nie rozumiałem trochę znaczenia rozpakowywania w tym czasie. Teraz po 4 latach uważam, że celem rozpakowania jest rozszerzenie kodu z jego pierwotnej zwartej formy. Oznacza to również usunięcie niejasności wokół tego obiektu, ponieważ nie jesteśmy pewni z definicji, czy jest zerowy czy nie. Podobnie jak powyższa odpowiedź Ashleya, pomyśl o tym jako o prezentie, który może w nim nic nie zawierać. Ale nadal uważam, że rozpakowywanie jest rozpakowywaniem kodu, a nie rozpakowywaniem opartym na pamięci, jak przy użyciu enum.
źródło
TL; DR
Co oznacza wykrzyknik w języku Swift?
Przykład
Źródło: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_399
źródło
let f: String! = "hello"
, a następnieprint(f)
, wyjście jestOptional("hello")
zamiast po prostu"hello"
.Gdyby John był opcjonalnym var (tak zadeklarowanym)
wtedy John mógłby nie mieć żadnej wartości (w języku ObjC, zero wartości)
Wykrzyknik w zasadzie mówi kompilatorowi: „Wiem, że ma wartość, nie trzeba jej testować”. Jeśli nie chcesz go używać, możesz go warunkowo przetestować:
Wnętrze tego ocenia tylko, czy John ma wartość.
źródło
“let possibleString: String? = "An optional string." println(possibleString!) // requires an exclamation mark to access its value // prints "An optional string.”
Ale działa dobrze bez! Coś tutaj wydaje się dziwne.John?
iJohn
są dwa różne typy. Jedna jest typu Osoba opcjonalna, a druga osoba typu. Osoba opcjonalna musi zostać rozpakowana, zanim będzie można ją wydobyć. Ale jak mówisz, wydaje się, że dzieje się tak przynajmniej w tych okolicznościach, bez konieczności robienia czegokolwiek. Wydaje się, że to sprawia! zbędny. Dopóki ! jest ZAWSZE opcjonalny, ale sugeruje, aby złapać błędy czasu kompilacji. Coś w rodzaju przypisania vars / let do określonego typu może być jawne,let John: Person = ...
ale można je również wywnioskowaćlet John = ...
.Niektóre duże perspektywy do dodania do innych przydatnych, ale bardziej szczegółowych odpowiedzi:
W Swift wykrzyknik pojawia się w kilku kontekstach:
let name = nameLabel!.text
var logo: UIImageView!
logo.image = thing as! UIImage
try! NSJSONSerialization.JSONObjectWithData(data, [])
Każda z nich jest inną konstrukcją językową o innym znaczeniu, ale wszystkie one mają trzy ważne cechy wspólne:
1. Wykrzykniki omijają kontrole bezpieczeństwa Swift w czasie kompilacji.
Kiedy używasz
!
w Swift, zasadniczo mówisz: „Hej, kompilatorze, wiem, że myślisz, że tutaj może się zdarzyć błąd , ale wiem z całkowitą pewnością, że nigdy nie nastąpi”.Nie wszystkie poprawne kody mieszczą się w polu systemu typu czas kompilacji Swift - czy w tym przypadku statycznym sprawdzaniu typu w dowolnym języku. Są sytuacje, w których można logicznie udowodnić, że błąd nigdy nie wystąpi, ale nie można tego udowodnić kompilatorowi . Właśnie dlatego projektanci Swift dodali te funkcje w pierwszej kolejności.
Jednak za każdym razem, gdy używasz
!
, wykluczasz ścieżkę odzyskiwania dla błędu, co oznacza, że…2. Wykrzykniki to potencjalne awarie.
Wykrzyknik mówi również: „Hej, Swift, jestem tak pewien, że ten błąd nigdy nie może się zdarzyć, że lepiej jest, abyś zawiesił całą moją aplikację niż dla mnie kodowanie ścieżki odzyskiwania”.
To niebezpieczne twierdzenie. To może być prawidłowa: w krytycznych kodu, w którym można było pomyśleć o ciężko niezmienników kodzie jest, być może, że wyjście podrobiony jest gorsza niż w wypadku.
Jednak gdy widzę
!
na wolności, rzadko używa się go tak uważnie. Zamiast tego zbyt często oznacza: „ta wartość była opcjonalna i tak naprawdę nie zastanawiałem się zbytnio, dlaczego może być zero lub jak właściwie poradzić sobie z tą sytuacją, ale dodanie!
sprawiło, że się skompilowała… więc mój kod jest poprawny, prawda?”Strzeż się arogancji wykrzyknika. Zamiast…
3. Wykrzykniki najlepiej stosować oszczędnie.
Każda z tych
!
konstrukcji ma?
odpowiednik, który zmusza do radzenia sobie z przypadkiem błędu / zeru:if let name = nameLabel?.text { ... }
var logo: UIImageView?
logo.image = thing as? UIImage
try? NSJSONSerialization.JSONObjectWithData(data, [])
Jeśli masz ochotę na użycie
!
, zawsze dobrze jest dokładnie rozważyć, dlaczego nie używasz?
. Czy!
awaria programu jest naprawdę najlepszą opcją, jeśli operacja się nie powiedzie? Dlaczego ta wartość jest opcjonalna / nie działa?Czy istnieje rozsądna ścieżka odzyskiwania, którą Twój kod może przyjąć w przypadku braku / błędu? Jeśli tak, koduj.
Jeśli nie może to być zero, jeśli błąd nigdy nie może się zdarzyć, to czy istnieje rozsądny sposób na poprawienie logiki, aby kompilator o tym wiedział? Jeśli tak, zrób to; twój kod będzie mniej podatny na błędy.
Są chwile, kiedy nie ma rozsądnego sposobu na poradzenie sobie z błędem, a po prostu zignorowanie błędu - a tym samym przejście do niewłaściwych danych - byłoby gorsze niż awaria. To są czasy, w których można użyć wymuszania rozpakowywania.
Okresowo przeszukuję całą bazę kodu
!
i sprawdzam każde jej użycie. Bardzo niewiele zastosowań stoi do kontroli. (W chwili pisania tego tekstu cała platforma Siesta ma dokładnie dwa jej wystąpienia ).Nie oznacza to, że nigdy nie powinieneś używać
!
w swoim kodzie - po prostu powinieneś używać go ostrożnie i nigdy nie ustawiać go jako opcji domyślnej.źródło
func isSubscriptionActive(receiptData: NSDictionary?) -> Bool { if(receiptData == nil) { return false; } return (hasValidTrial(receiptData!) || isNotExpired(receiptData!)) && isNotCancelled(receiptData!) }
Biorąc pod uwagę 3. czy jest lepszy sposób, aby to napisać?func isSubscriptionActive(receiptData: NSDictionary?) -> Bool { guard let nonNilReceiptData = receiptData else { return false} return (hasValidTrial(nonNilReceiptData) || isNotExpired(nonNilReceiptData)) && isNotCancelled(nonNilReceiptData) }
john
jest opcjonalnyvar
. Więc może zawieraćnil
wartość. Aby upewnić się, że wartość nie jest zerowa, użyj a!
na końcuvar
nazwy.Z dokumentacji
„Gdy masz pewność, że opcja zawiera wartość, możesz uzyskać dostęp do jej wartości podstawowej, dodając wykrzyknik (!) Na końcu nazwy opcji. Wykrzyknik skutecznie mówi: „Wiem, że to opcjonalne ma zdecydowanie wartość; skorzystaj z niego. ”
Innym sposobem sprawdzenia wartości innej niż zero jest
źródło
john.apartment = number73
mówi również: „Wiem, że to opcjonalne zdecydowanie ma wartość; proszę go użyć.” ...Oto kilka przykładów:
Gdzie
word
jest wartość opcjonalna. oznacza, że może zawierać wartość lub nie.Tutaj
name
ma wartość, którą możemy przypisaćGdzie
dog
jest mocno rozpakowane oznacza, że musi zawierać wartośćAplikacja ulegnie awarii, ponieważ jesteśmy przypisani
nil
do rozpakowanegoźródło
var c:Int = nil
otrzyma: „W tym przypadku...
var John: Osoba!
oznacza to, że początkowo John będzie miał wartość zerową, zostanie ustawiony, a raz ustawiony nigdy nie będzie ponownie zerowany. Dlatego dla wygody mogę użyć łatwiejszej składni w celu uzyskania dostępu do opcjonalnego var, ponieważ jest to „opcjonalnie nieopakowane opcjonalne”
źródło
Jeśli pochodzisz z języka rodziny C, będziesz myślał „wskaźnik do obiektu typu X, który może być adresem pamięci 0 (NULL)”, a jeśli pochodzisz z języka dynamicznego, będziesz myślenie „Obiekt, który prawdopodobnie jest typu X, ale może być niezdefiniowany”. Żadne z nich nie jest w rzeczywistości poprawne, chociaż w sposób okrężny pierwszy jest blisko.
Powinieneś o tym myśleć tak, jakby to był obiekt taki jak:
Kiedy testujesz swoją opcjonalną wartość
foo == nil
, naprawdę się zwracafoo.isNil
, i kiedy mówisz,foo!
że powracafoo.realObject
z twierdzeniem, żefoo.isNil == false
. Ważne jest, aby to zauważyć, ponieważ jeślifoo
tak naprawdę jest zerofoo!
, to jest to błąd czasu wykonywania, więc zwykle zamiast tego chcesz użyć warunkowego let, chyba że masz pewność, że wartość nie będzie zero. Ten rodzaj oszustwa oznacza, że język można silnie pisać bez konieczności sprawdzania, czy wartości są wszędzie zerowe.W praktyce tak się nie zachowuje, ponieważ kompilator wykonuje pracę. Na wysokim poziomie istnieje typ,
Foo?
który jest oddzielnyFoo
i który uniemożliwia funktom, które akceptują typ,Foo
otrzymanie wartości zerowej, ale na niskim poziomie wartość opcjonalna nie jest prawdziwym obiektem, ponieważ nie ma żadnych właściwości ani metod; jest prawdopodobne, że w rzeczywistości jest to wskaźnik, który może być NULL (0) z odpowiednim testem podczas wymuszania rozpakowywania.Istnieje inna sytuacja, w której zobaczysz wykrzyknik, na typie, na przykład:
Jest to w przybliżeniu równoznaczne z zaakceptowaniem opcjonalnego z wymuszonym rozpakowaniem, tj .:
Możesz użyć tego, aby uzyskać metodę, która technicznie akceptuje wartość opcjonalną, ale będzie miała błąd czasu działania, jeśli jest zerowy. W obecnej wersji Swift najwyraźniej omija to twierdzenie, że nie jest zero, więc zamiast tego wystąpi błąd niskiego poziomu. Zasadniczo nie jest to dobry pomysł, ale może być przydatny podczas konwersji kodu z innego języka.
źródło
The! oznacza, że wymusza się rozpakowywanie obiektu! następuje. Więcej informacji można znaleźć w dokumentacji Apple, którą można znaleźć tutaj: https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/TheBasics.html
źródło
Jeśli znasz C #, to jest jak typy dopuszczające wartości zerowe, które są również zadeklarowane za pomocą znaku zapytania:
A wykrzyknik w tym przypadku jest równoważny z dostępem do właściwości .Value typu zerowalnego, takiego jak to:
źródło
W obiektywnych zmiennych C bez wartości były równe „zero” (możliwe było również użycie wartości „zero” takich samych jak 0 i fałsz), dlatego możliwe było użycie zmiennych w instrukcjach warunkowych (Zmienne o wartościach takich samych jak „PRAWDA” ”, a te bez wartości były równe„ FAŁSZ ”).
Swift zapewnia bezpieczeństwo typu, zapewniając „wartość opcjonalną”. tzn. zapobiega powstawaniu błędów spowodowanych przypisywaniem zmiennych różnych typów.
Zatem w Swift można podawać tylko wartości logiczne w instrukcjach warunkowych.
Tutaj, chociaż „hw” jest ciągiem, nie można go użyć w instrukcji if, tak jak w celu C.
W tym celu należy go utworzyć jako
źródło
The! na końcu obiektu mówi, że obiekt jest opcjonalny i należy go rozpakować, jeśli w przeciwnym razie może zwrócić zero. Jest to często używane do wychwytywania błędów, które w przeciwnym razie spowodowałyby awarię programu.
źródło
W skrócie (!): Po zadeklarowaniu zmiennej i upewnieniu się, że zmienna przechowuje wartość.
w przeciwnym razie musiałbyś to robić po każdym przejściu wartości ...
źródło
John jest osobą opcjonalną, co oznacza, że może mieć wartość lub być zerową.
jest używany, jeśli John nie jest opcjonalny. Ponieważ John nigdy nie ma wartości zero, możemy być pewni, że nie zadzwoni on do mieszkania o wartości zero. Podczas
obiecuje kompilatorowi, że John nie ma wartości zero, a następnie rozpakowuje opcję opcjonalną, aby uzyskać wartość Johna i uzyskuje dostęp do właściwości apartamentu Johna. Użyj tego, jeśli wiesz, że John nie ma wartości zero. Jeśli nazywasz to opcjonalnie zero, pojawi się błąd czasu wykonywania.
Dokumentacja zawiera przyjemny przykład użycia tego, gdy przekonwertowany numer jest opcjonalny.
źródło
john.apartment = number73
, to NIE spowoduje błędu, chyba że wstawię „!” po janie?john
opcjonalności, nie mówi kompilatorowi, że muszę mieć wartośćvar jack: Person = john!
inną niż zero? ... A w twoim przykładzie, czy nie brakuje? po Osoba powiedz kompilatorowi, że musiszjohn
być inny niż zero? Ogólnie rzecz biorąc,!
wydaje się to po prostu zbędne ... Nadal wydaje mi się, że brakuje mi czegoś w części „dlaczego”!
...Mówiąc najprościej, wykrzykniki oznaczają, że opcja jest rozpakowywana. Opcjonalna jest zmienna, która może mieć wartość lub nie - więc możesz sprawdzić, czy zmienna jest pusta, używając instrukcji if let, jak pokazano tutaj , a następnie wymusić jej rozpakowanie. Jeśli jednak wymusisz rozpakowanie opcjonalnej, która jest pusta, program się zawiesi, więc bądź ostrożny! Opcje są deklarowane poprzez umieszczenie znaku zapytania na końcu wyraźnego przypisania do zmiennej, na przykład mógłbym napisać:
Ta zmienna nie ma wartości. Gdybym miał go rozpakować, program się zawiesiłby, a Xcode powiedziałby ci, że próbowałeś rozpakować opcjonalne o wartości zero.
Mam nadzieję, że to pomogło.
źródło
W PROSTYCH SŁOWACH
WYKORZYSTANIE Wykrzyknika wskazuje, że zmienna musi składać się z wartości innej niż zero (nigdy nie będzie zerowa)
źródło
Cała historia zaczyna się od funkcji szybkich zwanych opcjonalnymi zmiennymi. Są to zmienne, które mogą mieć wartość lub mogą nie mieć wartości. Ogólnie rzecz biorąc, swift nie pozwala nam na użycie zmiennej, która nie została zainicjowana, ponieważ może to prowadzić do awarii lub nieoczekiwanych przyczyn, a także do umieszczenia symbolu zastępczego dla backdoorów. Dlatego w celu zadeklarowania zmiennej, której wartość nie jest początkowo określona, używamy „?”. Kiedy taka zmienna jest zadeklarowana, aby użyć jej jako części wyrażenia, należy ją rozpakować przed użyciem, rozpakowywanie jest operacją, dzięki której odkryta jest wartość zmiennej, która dotyczy obiektów. Bez rozpakowywania, jeśli spróbujesz ich użyć, wystąpi błąd czasu kompilacji. Aby rozpakować zmienną, która jest opcjonalnym var, wykrzyknik „!” jest używany.
Teraz są chwile, kiedy wiesz, że takim opcjonalnym zmiennym zostaną przypisane wartości przez system na przykład lub własny program, ale jakiś czas później, na przykład gniazda interfejsu użytkownika, w takiej sytuacji zamiast deklarować opcjonalną zmienną za pomocą znaku zapytania „?” Używamy "!".
W ten sposób system wie, że ta zmienna zadeklarowana za pomocą „!” jest obecnie opcjonalny i nie ma wartości, ale otrzyma wartość w późniejszym okresie życia.
Tak więc wykrzyknik ma dwa różne zastosowania: 1. Aby zadeklarować zmienną, która będzie opcjonalna i na pewno otrzyma wartość później 2. 2. Aby rozpakować zmienną opcjonalną przed użyciem jej w wyrażeniu.
Mam nadzieję, że powyższe opisy pozwalają uniknąć zbyt wielu technicznych problemów.
źródło
Jeśli użyjesz go jako opcjonalnego, rozpakowuje opcjonalny i sprawdza, czy coś tam jest. Jeśli użyjesz go w instrukcji if-else to kod NIE. Na przykład,
źródło
Zmienna opcjonalna może zawierać wartość lub może nie być
przypadek 1:
var myVar:String? = "Something"
przypadek 2:
var myVar:String? = nil
teraz, jeśli poprosisz myVar !, mówisz kompilatorowi, aby zwrócił wartość w przypadku 1, że zwróci
"Something"
w przypadku 2 nastąpi awaria.
Znaczenie! znak zmusi kompilator do zwrócenia wartości, nawet jeśli jej nie ma. dlatego nazwa Force Unwrapping .
źródło
źródło
ZAPYTAJ SIEBIE
person?
maapartment
członka / właściwość? LUBperson
maapartment
członka / właściwość?Jeśli nie możesz odpowiedzieć na to pytanie, kontynuuj czytanie:
Aby to zrozumieć, możesz potrzebować super-podstawowego poziomu zrozumienia Generics . Zobacz tutaj . Wiele rzeczy w Swift zostało napisanych przy użyciu Generics. Zawiera opcje
Poniższy kod został udostępniony z tego filmu Stanford . Gorąco polecam obejrzeć pierwsze 5 minut
Opcjonalne to wyliczenie z tylko 2 przypadkami
Opcjonalne wiązanie:
kiedy mówisz, że
var john: Person?
tak naprawdę masz na myśli:Czy powyższe wyliczenie ma jakąkolwiek właściwość o nazwie
apartment
? Widzisz to gdzieś? W ogóle go tam nie ma! Jednak jeśli to rozpakujesz, tzn. Zróbperson!
to, możesz ... to, co robi pod maską, to:Optional<Person>.Some(Person(name: "John Appleseed"))
Gdybyś zdefiniował
var john: Person
zamiast:var john: Person?
wtedy nie musiałbyś już mieć!
używanego, ponieważPerson
sam ma członkaapartment
Jako przyszłą dyskusję na temat tego, dlaczego używanie
!
rozpakowywania nie jest czasami zalecane, zapoznaj się z tymi pytaniami i odpowiedziamiźródło