Co to jest „nieopakowana wartość” w języku Swift?

155

Uczę się Swift dla iOS 8 / OSX 10.10, postępując zgodnie z tym samouczkiem , a termin „ nieopakowana wartość ” jest używany kilka razy, jak w tym akapicie (w sekcji Obiekty i klasa ):

Podczas pracy z wartościami opcjonalnymi możesz pisać? przed operacjami, takimi jak metody, właściwości i indeksy dolne. Jeśli wartość przed? jest zero, wszystko po? jest ignorowane, a wartość całego wyrażenia wynosi zero. W przeciwnym razie wartość opcjonalna jest rozpakowywana , a wszystko po znaku? działa na nieopakowanej wartości . W obu przypadkach wartość całego wyrażenia jest wartością opcjonalną.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare?.sideLength

Nie rozumiem i bez powodzenia szukałem w internecie.

Co to znaczy?


Edytować

Z odpowiedzi Cezarego wynika niewielka różnica między wyjściem oryginalnego kodu a ostatecznym rozwiązaniem (przetestowanym na placu zabaw):

Oryginalny kod

Oryginalny kod

Rozwiązanie Cezarego

Rozwiązanie Cezarego

Właściwości nadklasy są wyświetlane na wyjściu w drugim przypadku, podczas gdy w pierwszym przypadku jest pusty obiekt.

Czy wynik nie powinien być identyczny w obu przypadkach?

Powiązane pytania i odpowiedzi: Jaka jest wartość opcjonalna w języku Swift?

Bigood
źródło
1
Odpowiedź Cezarego jest trafna. Jest też świetna książka wprowadzająca do Swift dostępna ZA DARMO na iBooks
Daniel Storm
@DanielStormApps Ten iBook jest przenośną wersją połączonego samouczka z mojego pytania :)
Bigood

Odpowiedzi:

289

Najpierw musisz zrozumieć, czym jest typ opcjonalny. Opcjonalny typ zasadniczo oznacza, że ​​zmienna może być nil .

Przykład:

var canBeNil : Int? = 4
canBeNil = nil

Znak zapytania wskazuje, że tak canBeNiljest nil.

To nie zadziała:

var cantBeNil : Int = 4
cantBeNil = nil // can't do this

Aby uzyskać wartość ze zmiennej, jeśli jest opcjonalna, musisz ją rozpakować . Oznacza to po prostu umieszczenie wykrzyknika na końcu.

var canBeNil : Int? = 4
println(canBeNil!)

Twój kod powinien wyglądać następująco:

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare!.sideLength

Przypis:

Możesz również zadeklarować opcje, aby automatycznie rozpakowywać, używając wykrzyknika zamiast znaku zapytania.

Przykład:

var canBeNil : Int! = 4
print(canBeNil) // no unwrapping needed

Zatem alternatywnym sposobem naprawy kodu jest:

let optionalSquare: Square! = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare.sideLength

EDYTOWAĆ:

Różnica, którą widzisz, jest dokładnie objawem tego, że wartość opcjonalna jest opakowana . Na wierzchu jest kolejna warstwa. Wersja nieopakowana pokazuje tylko prosty obiekt, ponieważ jest on, no cóż, rozpakowany.

Szybkie porównanie placu zabaw:

plac zabaw

W pierwszym i drugim przypadku obiekt nie jest automatycznie rozpakowywany, więc widzisz dwie „warstwy” ( {{...}}), podczas gdy w trzecim przypadku widzisz tylko jedną warstwę ( {...}), ponieważ obiekt jest automatycznie rozpakowywany.

Różnica między pierwszym a dwoma drugimi przypadkami polega na tym, że w przypadku dwóch drugich wystąpi błąd w czasie wykonywania, jeśli optionalSquarejest ustawiona na nil. Używając składni w pierwszym przypadku, możesz zrobić coś takiego:

if let sideLength = optionalSquare?.sideLength {
    println("sideLength is not nil")
} else {
    println("sidelength is nil")
}
Cezary Wójcik
źródło
1
Dzięki za jasne wyjaśnienie! Kod zawarty w moim pytaniu jest cytowany z samouczka Apple (link w pytaniu) i myślę, że powinien działać tak, jak jest (i działa). Jednak Twoje ostateczne rozwiązanie i oryginalne daje różne wyniki (zobacz mój tekst). Jakiekolwiek myśli?
Bigood
2
Chłodny. Świetne wyjaśnienie. Jednak nadal jestem zdezorientowany. Dlaczego miałbym chcieć użyć wartości opcjonalnej? Kiedy jest to przydatne? Dlaczego firma Apple stworzyła to zachowanie i składnię?
Todd Lehman
1
Jest to szczególnie istotne w przypadku właściwości klas. Swift wymaga, aby wszystkie metody nieopcjonalne były inicjowane w init()funkcji klasy. Polecam przeczytanie rozdziałów „Podstawy” (dotyczy opcji), „Inicjalizacja” i „Opcjonalne łączenie” w książce Swift wydanej przez Apple.
Cezary Wójcik
2
@ToddLehman Apple stworzył to, aby pomóc Ci pisać znacznie bezpieczniejszy kod. Np. Wyobraź sobie wszystkie wyjątki wskaźnika zerowego w Javie powodujące awarię programu, ponieważ ktoś zapomniał sprawdzić wartość null. Albo dziwne zachowanie na Objective-C, ponieważ zapomniałeś sprawdzić zero. Z typem opcjonalnym nie możesz zapomnieć o zera. Kompilator zmusza cię do zajęcia się tym.
Erik Engheim
5
@AdamSmith - Wychodząc z tła Java, zdecydowanie widzę użycie opcji opcjonalnych ... ale wychodząc również (ostatnio) z tła Objective-C, nadal mam problem z jego użyciem, ponieważ Objective- C jawnie pozwala na wysyłanie wiadomości do obiektów zerowych. Hmm. Bardzo chciałbym zobaczyć, jak ktoś pisze przykład tych, pokazujący, jak pomagają one w tworzeniu krótszego i bezpieczniejszego kodu niż równoważny kod, który ich nie używa. Nie mówię, że nie są przydatne; Mówię tylko, że jeszcze nie „rozumiem”.
Todd Lehman
17

Istniejąca prawidłowa odpowiedź jest świetna, ale stwierdziłem, że aby w pełni to zrozumieć, potrzebowałem dobrej analogii, ponieważ jest to bardzo abstrakcyjna i dziwna koncepcja.

Pozwólcie więc, że pomogę tym innym programistom o „praworęcznych” (myślących wizualnie), podając inną perspektywę oprócz poprawnej odpowiedzi. Oto dobra analogia, która bardzo mi pomogła.

Analogia pakowania prezentów urodzinowych

Pomyśl o opcjach jak o prezentach urodzinowych, które są dostarczane w sztywnym, twardym, kolorowym opakowaniu.

Nie wiesz, czy coś jest w opakowaniu, dopóki nie rozpakujesz prezentu - może w ogóle nic nie ma w środku! Jeśli coś jest w środku, może to być kolejny prezent, który również jest zapakowany i który również może nic nie zawierać . Możesz nawet rozpakować 100 zagnieżdżonych prezentów, aby w końcu odkryć, że nie było nic poza opakowaniem .

Jeśli wartość opcjonalna nie jest nil, teraz odkryłeś pudełko zawierające coś . Ale zwłaszcza jeśli wartość nie jest jawnie wpisana i jest zmienną, a nie predefiniowaną stałą, może być konieczne otwarcie pola, zanim będziesz mógł dowiedzieć się czegoś konkretnego o tym, co jest w polu, np. Jaki to jest typ lub jaki jest rzeczywista wartość to.

Co jest w pudełku?! Analogia

Nawet po rozpakowaniu zmiennej nadal jesteś jak Brad Pitt z ostatniej sceny w SE7EN ( ostrzeżenie : spoilery i wulgarny język i przemoc z bardzo oceną R), ponieważ nawet po rozpakowaniu prezentu jesteś w następującej sytuacji: masz teraz nilalbo pudełko zawierające coś (ale nie wiesz co).

Możesz znać typ czegoś . Na przykład, jeśli zadeklarowałeś zmienną jako typ, [Int:Any?]wiedziałbyś, że masz (potencjalnie pusty) słownik z indeksami całkowitymi, które dają opakowaną zawartość dowolnego starego typu.

Dlatego zajmowanie się typami kolekcji (słownikami i tablicami) w języku Swift może stać się trochę owłosione.

Przykładem:

typealias presentType = [Int:Any?]

func wrap(i:Int, gift:Any?) -> presentType? {
    if(i != 0) {
        let box : presentType = [i:wrap(i-1,gift:gift)]
        return box
    }
    else {
        let box = [i:gift]
        return box
    }
}

func getGift() -> String? {
    return "foobar"
}

let f00 = wrap(10,gift:getGift())

//Now we have to unwrap f00, unwrap its entry, then force cast it into the type we hope it is, and then repeat this in nested fashion until we get to the final value.

var b4r = (((((((((((f00![10]! as! [Int:Any?])[9]! as! [Int:Any?])[8]! as! [Int:Any?])[7]! as! [Int:Any?])[6]! as! [Int:Any?])[5]! as! [Int:Any?])[4]! as! [Int:Any?])[3]! as! [Int:Any?])[2]! as! [Int:Any?])[1]! as! [Int:Any?])[0])

//Now we have to DOUBLE UNWRAP the final value (because, remember, getGift returns an optional) AND force cast it to the type we hope it is

let asdf : String = b4r!! as! String

print(asdf)
CommaToast
źródło
ładne 😊 😊😊 😊 😊😊 😊 😊😊
SamSol
uwielbiam tę analogię! Czy jest więc lepszy sposób na rozpakowanie pudełek? w przykładowym kodzie
David T.
W pudełku jest kot Schrödingera
Jacksonkr
Dzięki za zmieszanie mnie… Jaki programista daje prezent urodzinowy, który tak czy inaczej jest pusty? kinda mean
delta2flat
@Jacksonkr Albo nie.
amsmath
13

Swift kładzie duży nacisk na bezpieczeństwo typów. Cały język Swift został zaprojektowany z myślą o bezpieczeństwie. To jedna z cech charakterystycznych Swifta, którą należy powitać z otwartymi ramionami. Pomoże w opracowaniu czystego, czytelnego kodu i pomoże zapobiec awariom aplikacji.

Wszystkie opcje w Swift są oznaczone ?symbolem. Ustawiając ?po nazwie typu, w którym deklarujesz jako opcjonalny, zasadniczo rzutujesz to nie jako typ, w którym znajduje się przed ?, ale zamiast tego jako typ opcjonalny .


Uwaga: zmienna lub typ Intto nie to samo co Int?. Są to dwa różne typy, które nie mogą działać na sobie nawzajem.


Korzystanie z opcjonalnego

var myString: String?

myString = "foobar"

Ten sposób nie oznacza, że użytkownik pracuje z typem String. Oznacza to, że pracujesz z typem String?(ciąg opcjonalny lub opcjonalny ciąg). W rzeczywistości za każdym razem, gdy spróbujesz

print(myString)

w czasie wykonywania wydrukuje konsola debugowania Optional("foobar"). Część „ Optional()” wskazuje, że ta zmienna może, ale nie musi, mieć wartość w czasie wykonywania, ale tak się składa, że ​​obecnie zawiera ciąg „foobar”. To " Optional()" wskazanie pozostanie, chyba że zrobisz tak zwane "" rozpakowywanie "opcjonalnej wartości.

Rozpakowanie opcjonalnego oznacza, że ​​teraz rzutujesz ten typ jako nieopcjonalny. Spowoduje to wygenerowanie nowego typu i przypisanie wartości, która znajdowała się w tym opcjonalnym, do nowego nieopcjonalnego typu. W ten sposób można wykonywać operacje na tej zmiennej, ponieważ kompilator gwarantuje, że ma ona stałą wartość.


Warunkowe rozpakowanie sprawdzi, czy wartość w opcjonalnym jest, nilczy nie. Jeśli tak nie jest nil, pojawi się nowo utworzona stała zmienna, której zostanie przypisana wartość i zostanie rozpakowana do nieopcjonalnej stałej. A stamtąd możesz bezpiecznie używać nieopcjonalnego w ifbloku.

Uwaga: Stałej warunkowo nieopakowanej można nadać tę samą nazwę, co rozpakowywanej zmiennej opcjonalnej.

if let myString = myString {
    print(myString)
    // will print "foobar"
}

Warunkowe rozpakowanie opcji jest najczystszym sposobem uzyskania dostępu do wartości opcjonalnej, ponieważ jeśli zawiera ona wartość zerową, to wszystko w bloku if let nie zostanie wykonane. Oczywiście, tak jak w przypadku każdej instrukcji if, możesz dołączyć blok else

if let myString = myString {
    print(myString)
    // will print "foobar"
}
else {
    print("No value")
}

Wymuszone rozpakowywanie odbywa się za pomocą tak zwanego !operatora („bang”). Jest to mniej bezpieczne, ale nadal umożliwia kompilację kodu. Jednak za każdym razem, gdy używasz operatora bang, musisz mieć 1000% pewności, że zmienna faktycznie zawiera stałą wartość, zanim zostanie wymuszona rozpakowana.

var myString: String?

myString = "foobar"

print(myString!)

Powyższy kod jest całkowicie prawidłowym kodem Swift. Wyświetla wartość, myStringktóra została ustawiona jako „foobar”. Użytkownik zobaczy foobarwydrukowany w konsoli i to wszystko. Ale załóżmy, że wartość nigdy nie została ustawiona:

var myString: String?

print(myString!) 

Teraz mamy inną sytuację na naszych rękach. W przeciwieństwie do Objective-C, za każdym razem, gdy podejmowana jest próba wymuszonego rozpakowania opcjonalnego, a opcjonalny nie został ustawiony i jest nil, gdy spróbujesz rozpakować opcjonalny, aby zobaczyć, co jest w środku, aplikacja ulegnie awarii.


Rozpakowywanie z rzutowaniem typów . Jak powiedzieliśmy wcześniej, chociaż jesteś unwrappingopcjonalnym, w rzeczywistości rzutujesz na typ nieopcjonalny, możesz również rzutować nieopcjonalny na inny typ. Na przykład:

var something: Any?

Gdzieś w naszym kodzie zmienna somethingotrzyma jakąś wartość. Może używamy leków generycznych, a może istnieje inna logika, która spowoduje zmianę tego. Więc później w naszym kodzie chcemy użyć, somethingale nadal możemy traktować go inaczej, jeśli jest to inny typ. W takim przypadku będziesz chciał użyć assłowa kluczowego, aby to ustalić:

Uwaga: asoperator to sposób wpisywania rzutowania w języku Swift.

// Conditionally
if let thing = something as? Int {
    print(thing) // 0
}

// Optionally
let thing = something as? Int
print(thing) // Optional(0)

// Forcibly
let thing = something as! Int
print(thing) // 0, if no exception is raised

Zwróć uwagę na różnicę między tymi dwoma assłowami kluczowymi. Tak jak poprzednio, gdy siłą rozpakowaliśmy element opcjonalny, używaliśmy do tego !operatora bang. Tutaj zrobisz to samo, ale zamiast rzucać jako nieobowiązkowe, rzucasz również jako Int. I musi być możliwe do obniżenia, ponieważ Intw przeciwnym razie, na przykład przy użyciu operatora bang, gdy wartością jest, nilaplikacja ulegnie awarii.

Aby w ogóle użyć tych zmiennych w jakiejś operacji matematycznej, muszą zostać rozpakowane, aby to zrobić.

Na przykład w Swift tylko prawidłowe typy danych liczbowych tego samego rodzaju mogą być obsługiwane na sobie nawzajem. Kiedy rzucasz typ za pomocą as!, wymuszasz obniżenie tej zmiennej, tak jakbyś był pewien, że jest tego typu, dlatego bezpieczna jest operacja i nie zawieszanie aplikacji. Jest to w porządku, o ile zmienna rzeczywiście jest typu, do którego ją rzucasz, w przeciwnym razie będziesz mieć bałagan na rękach.

Niemniej jednak rzutowanie za pomocą as!pozwoli na kompilację kodu. Casting z an as?to inna historia. W rzeczywistości as?deklaruje razem Intjako zupełnie inny typ danych.

Teraz jest Optional(0)

A jeśli kiedykolwiek próbowałeś odrobić pracę domową, pisząc coś takiego

1 + Optional(1) = 2

Twój nauczyciel matematyki prawdopodobnie dałby ci „F”. To samo z Swift. Tyle że Swift wolałby raczej w ogóle nie kompilować, niż wystawiać ocenę. Ponieważ pod koniec dnia opcja opcjonalna może w rzeczywistości być zerowa .

Bezpieczeństwo przede wszystkim.

Brandon A.
źródło
Ładne wyjaśnienie opcjonalnych typów. Pomogło mi to wyjaśnić.
Tobe_Sta
0

'?' oznacza opcjonalne wyrażenie łańcuchowe,

„!” oznacza wartość siły
wprowadź opis obrazu tutaj

gkgy
źródło