Co oznacza wykrzyknik w języku Swift?

518

Przewodnik po języku programowania Swift zawiera następujący przykład:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { println("\(name) is being deinitialized") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { println("Apartment #\(number) is being deinitialized") }
}

var john: Person?
var number73: Apartment?

john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)

//From Apple's “The Swift Programming Language” guide (https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html)

Następnie, przypisując mieszkanie do osoby, używają wykrzyknika, aby „rozpakować instancję”:

john!.apartment = number73

Co to znaczy „rozpakować instancję”? Dlaczego jest to konieczne? Czym różni się to od wykonywania następujących czynności:

john.apartment = number73

Jestem bardzo nowy w języku Swift. Po prostu próbuję opanować podstawy.


AKTUALIZACJA:
Dużym fragmentem układanki, którego mi brakowało (nie podanym bezpośrednio w odpowiedziach - przynajmniej nie w momencie pisania tego) jest to, że wykonujesz następujące czynności:

var john: Person?

to wcale NIE oznacza, że ​​„ johnjest typu Personi może być zero”, jak pierwotnie myślałem. Po prostu źle to zrozumiałem Personi Person?są to całkowicie odrębne typy. Kiedy to zrozumiałem, wszystko inne ?, !szaleństwo i wspaniałe odpowiedzi poniżej, miały o wiele większy sens.

Troja
źródło

Odpowiedzi:

530

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 Tz Some(T)).

Czym się john!.apartment = number73róż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 johnto nie jest przypadek Personi nie ma apartmentczłonka:

john.apartment
// 'Person?' does not have a member named 'apartment'

Rzeczywistą Personwartość można rozpakować na różne sposoby:

  • „wymuszone rozpakowywanie”: john!(podaje Personwartość, jeśli istnieje, błąd czasu wykonywania, jeśli jest zerowy)
  • „opcjonalne wiązanie”: if let p = john { println(p) }(wykonuje, printlnjeś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 johnzmienna 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 johnmoż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.

Ashley
źródło
Nie jestem pewien co do tego wyjaśnienia. Jeśli po prostu uruchomisz kod bez! nadal zwraca rzeczywistą wartość. Może ! jest dla prędkości?
Richard Washington,
OK. Więc doktorzy mówią o używaniu! kiedy wiesz na pewno, można go rozpakować. Ale możesz uruchomić kod w porządku bez niego (czwarta opcja na liście - niejawne rozpakowywanie) ORAZ bez wcześniejszego sprawdzania. Otrzymasz wartość zero lub zero, jeśli zero. Ale jeśli wiesz na pewno, że to nie zero, użyj! ...... ale nadal nie rozumiem, dlaczego to zrobiłeś?
Richard Washington,
2
Cześć @RichardWashington - Dodałem aktualizację do mojej odpowiedzi, która, mam nadzieję, wyjaśnia niektóre z nich.
Ashley,
Aktualizacja 3 jest dokładnie tym, czego szukam. Przed przeczytaniem myślałem, że Apple kłamie, kiedy zwrócili coś w rodzaju NSURLCredential! który faktycznie może być zerowy.
Golden Thumb
Dzięki za fantastyczną odpowiedź @ Ashley. Po przeczytaniu tej odpowiedzi i kilku przykładowych szybkich kodów Apple naprawdę wydaje mi się, że w tym kompromisie między bezpieczeństwem a produktywnością zwykle najlepiej jest być po bezpieczniejszej stronie. Znamiennym wyjątkiem, który znalazłem, jest to, że Apple stosuje wymuszone rozpakowywanie elementów interfejsu użytkownika, co ma sens, ponieważ po pierwsze elementy interfejsu są zwykle opcjonalne, gdy dotyczą scenorysu (ponieważ nie są programowo przypisywane wartości), a po drugie, ponieważ są prawie nigdy zero, chyba że ich kontroler widoku zawiera zero, w takim przypadku dyskusja jest możliwa.
Mihir,
127

Oto, jak myślę, różnica:

var john: Person?

Oznacza, że ​​John może być zerowy

john?.apartment = number73

Kompilator zinterpretuje ten wiersz jako:

if john != nil {
    john.apartment = number73
}

Podczas

john!.apartment = number73

Kompilator zinterpretuje tę linię jako po prostu:

john.apartment = number73

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.

Amr
źródło
9
Na moim placu zabaw john.apartment = number73 się nie kompiluje, musisz podać john? .Apartment = number73
Chuck Pinkert
2
@ ChuckPinkert ma rację, czwartą linię należy zmienić na John? .Apartment = number73, choć fajna odpowiedź!
Bruce
john.apartment = liczba73 podaje błąd: wartość typu opcjonalnego „Osoba?” nie rozpakowano: czy chciałeś użyć „!” lub „?”?
Spider
4
Rozpakowywanie nie ma nic wspólnego z wydajnością wrt. „Kontrola zerowa” nadal musi być wykonana w czasie wykonywania, jedyną różnicą jest to, że błąd środowiska wykonawczego zostanie wyrzucony w przypadku braku wartości w Opcjonalnym.
madidier
67

TL; DR

Co oznacza wykrzyknik w języku Swift?

Wykrzyknik skutecznie mówi: „Wiem, że to opcjonalne ma zdecydowanie wartość; skorzystaj z niego. ” Jest to znane jako wymuszone rozpakowywanie wartości opcjonalnej:

Przykład

let possibleString: String? = "An optional string."
print(possibleString!) // requires an exclamation mark to access its value
// prints "An optional string."

let assumedString: String! = "An implicitly unwrapped optional string."
print(assumedString)  // no exclamation mark is needed to access its value
// prints "An implicitly unwrapped optional string."

Ź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

Alex Nolasco
źródło
11
Twoja odpowiedź jest świetna, ponieważ rozumiem, co się teraz dzieje. Nie rozumiem, dlaczego istnieją niejawnie nieopakowane opcje. Po co tworzyć coś zdefiniowanego jako domyślnie nieopakowany opcjonalny ciąg, a nie zwykły typ ciągu? Ich użycie po tym jest takie samo. czego mi brakuje?
pachun,
To nie wydaje się już prawdą. W Swift 5 repl, jeśli to zrobię let f: String! = "hello", a następnie print(f), wyjście jest Optional("hello")zamiast po prostu "hello".
numbermaniac
37

Gdyby John był opcjonalnym var (tak zadeklarowanym)

var john: Person?

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

if let otherPerson = john {
    otherPerson.apartment = number73
}

Wnętrze tego ocenia tylko, czy John ma wartość.

Ben Gottlieb
źródło
4
Dzięki za odpowiedź, teraz rozumiem, co mówi wykrzyknik, wciąż próbuję zawinąć głowę, dlaczego ... Powiedziałeś, że mówi kompilatorowi: „Wiem, że to ma wartość, nie musisz testować to". Co kupuje, gdy kompilator go nie testuje? Czy bez wykrzyknika kompilator zgłosi błąd (nie mam jeszcze środowiska, w którym mogę przetestować Swift)? Jest ! W 100% konieczne dla wszystkich opcjonalnych zmiennych? Jeśli tak, to dlaczego Apple tak się tym przejmowało, a nie tylko brakowało ! oznacza „Wiem, że to ma wartość, nie musisz jej testować”?
Troy
„czy kompilator zgłosi błąd” - nie, nadal działa dobrze, zwracając wartość zgodnie z oczekiwaniami. Nie rozumiem tego Jest ! tylko dla prędkości, kiedy możesz być pewien?
Richard Washington,
W rzeczywistości później dokumenty mówią o niejawnie nieopakowanych opcjach na poniższym przykładzie: “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.
Richard Washington,
3
@RichardWashington Jesteś zdezorientowany nie bez powodu! Przykłady w dokumentach pomijają niektóre rzeczy i są mylące, ponieważ wszystko, co robią, to używanie println. Najwyraźniej istnieje co najmniej kilka przypadków, w których rozpakowywanie opcji nie jest wymagane. Jednym z nich jest użycie println. Innym jest użycie interpolacji łańcuchów. Więc może interpolacja println i stringów rozpakowuje się pod okładkami? Może ktoś ma na to lepszy wgląd. Patrzenie na definicje nagłówków Beta Xcode6 nie ujawniło mi nic na temat tej magii.
mbeaty
Tak, rozumiem John?i Johnsą 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 = ....
Richard Washington,
27

Niektóre duże perspektywy do dodania do innych przydatnych, ale bardziej szczegółowych odpowiedzi:

W Swift wykrzyknik pojawia się w kilku kontekstach:

  • Wymuszone rozpakowywanie: let name = nameLabel!.text
  • Niejawnie nieopakowane opcje: var logo: UIImageView!
  • Wymuszone odlewanie: logo.image = thing as! UIImage
  • Nieobsługiwane wyjątki: 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:

  • Warunkowe rozpakowywanie: if let name = nameLabel?.text { ... }
  • Opcje: var logo: UIImageView?
  • Obsady warunkowe: logo.image = thing as? UIImage
  • Wyjątki od braku awarii: 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.

Paul Cantrell
ź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ć?
jenson-button-event
Możesz powiedziećfunc isSubscriptionActive(receiptData: NSDictionary?) -> Bool { guard let nonNilReceiptData = receiptData else { return false} return (hasValidTrial(nonNilReceiptData) || isNotExpired(nonNilReceiptData)) && isNotCancelled(nonNilReceiptData) }
rkgibson2
Wykrzykniki nie omijają żadnych kontroli bezpieczeństwa. Otrzymujesz gwarantowaną awarię, jeśli opcja jest zerowa. Opcjonalne jest zawsze zaznaczone. To tylko bardzo brutalny sposób sprawdzenia bezpieczeństwa.
gnasher729
@ gnasher729 Jak stwierdzono w odpowiedzi, omija on kontrole bezpieczeństwa w czasie kompilacji na rzecz bezpiecznej awarii środowiska wykonawczego .
Paul Cantrell,
24

johnjest opcjonalny var. Więc może zawierać nilwartość. Aby upewnić się, że wartość nie jest zerowa, użyj a !na końcu varnazwy.

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

    if let j = json {
        // do something with j
    }
Smażyć
źródło
1
Tak, widziałem to w dokumentacji, ale nadal wydaje się to niepotrzebne ... ponieważ, jak dla mnie, john.apartment = number73mówi również: „Wiem, że to opcjonalne zdecydowanie ma wartość; proszę go użyć.” ...
Troy
6
Tak, ale chodzi o to, że domyślnie Swift próbuje wykryć błędy w czasie kompilacji. Jeśli wiesz lub uważasz, że wiesz, że ta zmienna nie może zawierać wartości zerowej, możesz usunąć tę kontrolę, używając wykrzyknika.
lassej
16

Oto kilka przykładów:

var name:String = "Hello World"
var word:String?

Gdzie wordjest wartość opcjonalna. oznacza, że ​​może zawierać wartość lub nie.

word = name 

Tutaj namema wartość, którą możemy przypisać

var cow:String = nil
var dog:String!

Gdzie dogjest mocno rozpakowane oznacza, że ​​musi zawierać wartość

dog = cow

Aplikacja ulegnie awarii, ponieważ jesteśmy przypisani nildo rozpakowanego

Ramkumar chintala
źródło
1
var c:Int = nilotrzyma: „
Zero
15

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”

Gość
źródło
1
Dziękuję Ci. Znalazłem również następujący link, który to wyjaśnia. Ten typ jest nazywany „niejawnie nieopakowaną opcją”. stackoverflow.com/questions/24122601/…
Kaydell
5

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:

struct Optional<T> {
   var isNil:Boolean
   var realObject:T
}

Kiedy testujesz swoją opcjonalną wartość foo == nil, naprawdę się zwraca foo.isNil, i kiedy mówisz, foo!że powraca foo.realObjectz twierdzeniem, że foo.isNil == false. Ważne jest, aby to zauważyć, ponieważ jeśli footak naprawdę jest zero foo!, 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 oddzielny Fooi który uniemożliwia funktom, które akceptują typ, Foootrzymanie 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:

func foo(bar: String!) {
    print(bar)
}

Jest to w przybliżeniu równoznaczne z zaakceptowaniem opcjonalnego z wymuszonym rozpakowaniem, tj .:

func foo(bar: String?) {
    print(bar!)
}

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.

Jim Driscoll
źródło
3

Jeśli znasz C #, to jest jak typy dopuszczające wartości zerowe, które są również zadeklarowane za pomocą znaku zapytania:

Person? thisPerson;

A wykrzyknik w tym przypadku jest równoważny z dostępem do właściwości .Value typu zerowalnego, takiego jak to:

thisPerson.Value
Abdurrahman
źródło
2

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.

var hw = "Hello World"

Tutaj, chociaż „hw” jest ciągiem, nie można go użyć w instrukcji if, tak jak w celu C.

//This is an error

if hw

 {..}

W tym celu należy go utworzyć jako

var nhw : String? = "Hello World"

//This is correct

if nhw

 {..}
Gokul
źródło
2

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.

cheborneck
źródło
2

W skrócie (!): Po zadeklarowaniu zmiennej i upewnieniu się, że zmienna przechowuje wartość.

let assumedString: String! = "Some message..."
let implicitString: String = assumedString

w przeciwnym razie musiałbyś to robić po każdym przejściu wartości ...

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // requires an exclamation mark
En Hui Lim
źródło
1

John jest osobą opcjonalną, co oznacza, że ​​może mieć wartość lub być zerową.

john.apartment = number73

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

john!.apartment = number73

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.

if convertedNumber {
    println("\(possibleNumber) has an integer value of \(convertedNumber!)")
} else {
    println("\(possibleNumber) could not be converted to an integer")
}
Connor
źródło
Czy mówisz, że jeśli John jest zero, a ja idę john.apartment = number73, to NIE spowoduje błędu, chyba że wstawię „!” po janie?
Troy
Jeśli John jest opcjonalny, musisz użyć wykrzyknika, aby uzyskać dostęp do jego właściwości, ponieważ kompilator musi wiedzieć, że nie jest zero. Jeśli nie jest to opcjonalne, nie możesz użyć wykrzyknika.
Connor,
1
Jeśli muszę użyć wykrzyknika dla WSZYSTKICH opcjonalnych zmiennych, to dlaczego Apple nawet się tym przejmował, w przeciwieństwie do tego, że brak wykrzyknika oznacza to samo? Wydaje się to niepotrzebnym wzdęciem ... Czy może jest sens NIE używać wykrzyknika z opcjonalnym var?
Troy
używasz wykrzyknika, gdy potrzebujesz opcji, aby nie była zerowa. Podobnie jak w przypadku dostępu do właściwości Johna. możesz zrobić coś takiego jak var jack: Person? = John, gdzie Jack jest również opcjonalny lub var Jack: Osoba = John! gdzie jack jest Osobą i nie ma wartości zero
Connor
Zgadza się, ale w oryginalnym przykładzie fakt, że rezygnuję z johnopcjonalnoś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 musisz johnbyć 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” !...
Troy
1

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

var optionalExample: String?

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.

siarka
źródło
1

W PROSTYCH SŁOWACH

WYKORZYSTANIE Wykrzyknika wskazuje, że zmienna musi składać się z wartości innej niż zero (nigdy nie będzie zerowa)

Maninderjit Singh
źródło
1

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.

Vishal Dharankar
źródło
1

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,

if (myNumber != 3){
 // if myNumber is NOT 3 do whatever is inside these brackets.
)
Andy Lebowitz
źródło
1

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 .

Ashish Pisey
źródło
@Moritz. Nie wiem czy to tak? Czy mogę odpowiedzieć, jeśli pytanie jest już udzielone? Czy powoduję jakiś problem lub czy w mojej odpowiedzi jest coś nie tak? nie rozumiem, dlaczego przegłosowałeś to, aby odpowiedzieć poprawnie? Próbowałem odpowiedzieć na podstawie mojego zrozumienia tego pojęcia. myślę, że niektórzy uważają, że jest to pomocne za jasne i łatwe wyjaśnienie.
Ashish Pisey
@Moritz Teraz jest to konstruktywne. Powinieneś był mi powiedzieć tę korektę, jeśli to był powód. Dzięki za opinie. poprawiłem to.
Ashish Pisey,
0
Simple the Optional variable allows nil to be stored.

var str : String? = nil

str = "Data"

To convert Optional to the Specific DataType, We unwrap the variable using the keyword "!"

func get(message : String){
   return
}

get(message : str!)  // Unwapped to pass as String
Jeff Ayan
źródło
1
Proszę nie pisać odpowiedzi, które nie dodają niczego, co nie zostało jeszcze uwzględnione w poprzednich odpowiedziach.
Dalija Prasnikar
-1

ZAPYTAJ SIEBIE

  • Czy typ person?maapartment członka / właściwość? LUB
  • Czy typ personma apartmentczł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

enum Optional<T>{
    case None
    case Some(T)
}

let x: String? = nil //actually means:

let x = Optional<String>.None

let x :String? = "hello" //actually means:

let x = Optional<String>.Some("hello")

var y = x! // actually means:

switch x {
case .Some(let value): y = value
case .None: // Raise an exception
}

Opcjonalne wiązanie:

let x:String? = something
if let y = x {
    // do something with y
}
//Actually means:

switch x{
case .Some(let y): print)(y) // or whatever else you like using 
case .None: break
}

kiedy mówisz, że var john: Person?tak naprawdę masz na myśli:

enum Optional<Person>{
case .None
case .Some(Person)
}

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ób person!to, możesz ... to, co robi pod maską, to:Optional<Person>.Some(Person(name: "John Appleseed"))


Gdybyś zdefiniował var john: Personzamiast: var john: Person?wtedy nie musiałbyś już mieć !używanego, ponieważ Personsam ma członkaapartment


Jako przyszłą dyskusję na temat tego, dlaczego używanie !rozpakowywania nie jest czasami zalecane, zapoznaj się z tymi pytaniami i odpowiedziami

miód
źródło
2
Korzystanie z powyższych slajdów jest prawdopodobnie naruszeniem praw autorskich: „... Uniwersytet Stanford zachowuje prawa autorskie do wszystkich treści w naszej kolekcji iTunes U”. , z http://itunes.stanford.edu . Prawdopodobnie lepiej odpowiedzieć, własnymi słowami, na treść, której nauczyłeś się z kursu, który postrzegasz jako odpowiedź na to pytanie (i zamiast korzystać ze slajdów, raczej przytaczaj odpowiednie części w formie kodu, z referencjami, oczywiście).
dfri
2
Twój argument to argumentum ad populum . W każdym razie nie wierzę, że Stanford przyjedzie tutaj i będzie egzekwować prawa DMCA , ale zawsze lepiej, jeśli twoje odpowiedzi są własnymi słowami w oparciu o oficjalne / ważne źródła , a nie w formie kopiowania tych źródeł jak wyżej; szczególnie, gdy mówimy o slajdach chronionych prawem autorskim: ponownie lepiej cytować zawartość slajdów w blokach kodu (obrazy kodu są generalnie nie-nie w SO!).
dfri
1
Meta post, do którego odsyłam, opisuje tylko podejście SO: do tego: post taki jak ten naturalnie nie zostanie usunięty, jeśli jakiś przypadkowy użytkownik zgłosi to jako naruszenie praw autorskich: tylko jeśli np. Przedstawiciel Stanforda. mieli przyjść tutaj, a ponieważ post, który ma zostać usunięty, SO musiałby go egzekwować. Dlatego napisałem: Nie wierzę, że Stanford tu przyjedzie i będzie egzekwował prawa DMCA ...” . Chodzi mi o to, że uważam, że może to być naruszenie praw autorskich (co uważam za błędne), ale oczywiście nikt nigdy nie będzie tego egzekwował. ...
dfri
1
Ale poza tym, zamieszczanie pytań lub odpowiedzi zawierających obrazy kodu jest odradzane z wielu powodów. Podsumowując: starałem się wskazać w tych komentarzach, że uważam, że odpowiedź w obecnej formie nie jest tak dobra, jak mogłaby być, z dwóch głównych powodów (slajdy chronione prawem autorskim, obraz kodu). Oczywiście nikt, w tym ja, nic na to nie poradzę, po prostu wskazuję to na przyszłość (lub ewentualnie zachęcam do edytowania tego postu z cytatem zamiast bloków kodu). Zgoda, iTunes U! :)
dfri
2
@dfri usunął zdjęcia
Honey