Obniżanie opcji w Swift: as? Wpisz lub as! Rodzaj?

96

Biorąc pod uwagę, że w Swift:

var optionalString: String?
let dict = NSDictionary()

Jaka jest praktyczna różnica między następującymi dwoma stwierdzeniami:

optionalString = dict.objectForKey("SomeKey") as? String

vs

optionalString = dict.objectForKey("SomeKey") as! String?
sdduursma
źródło

Odpowiedzi:

143

Praktyczna różnica jest taka:

var optionalString = dict["SomeKey"] as? String

optionalStringbędzie zmienną typu String?. Jeśli typ bazowy jest inny niż a, Stringto zostanie po prostu przypisany nildo opcjonalnego.

var optionalString = dict["SomeKey"] as! String?

To mówi, wiem , że to jest String?. To również spowoduje optionalStringbycie typu String?, ale ulegnie awarii, jeśli typ bazowy jest inny.

Pierwszy styl jest następnie używany z, if letaby bezpiecznie rozpakować opcjonalne:

if let string = dict["SomeKey"] as? String {
    // If I get here, I know that "SomeKey" is a valid key in the dictionary, I correctly
    // identified the type as String, and the value is now unwrapped and ready to use.  In
    // this case "string" has the type "String".
    print(string)
}
vacawama
źródło
Czy zatem pierwsza metoda nie jest zawsze lepsza? Oba zwracają opcjonalny typ String? Wydaje się, że druga metoda robi to samo, co pierwsza, ale może się zawiesić, jeśli obniżenie się nie powiedzie. Więc po co go w ogóle używać?
Sikander
6
Tak @Sikander, pierwszy jest zawsze lepszy. Nigdy nie użyłbym drugiego.
vacawama
14

as? Types- oznacza, że ​​proces odlewania w dół jest opcjonalny. Proces może zakończyć się sukcesem lub nie (system zwróci zero, jeśli rzucanie w dół się nie powiedzie). Żaden sposób nie ulegnie awarii, jeśli rzucanie w dół się nie powiedzie.

as! Type?- Tutaj proces rzucania w dół powinien zakończyć się sukcesem ( !wskazuje na to). Końcowy znak zapytania wskazuje, czy wynik końcowy może być zerowy, czy nie.

Więcej informacji dotyczących "!" i "?"

Weźmy 2 przypadki

  1. Rozważać:

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell
    

    Tutaj nie wiemy, czy wynik rzutowania komórki z identyfikatorem „Cell” na UITableViewCell jest sukcesem, czy nie. Jeśli się nie powiedzie, zwraca zero (więc unikamy tutaj awarii). Tutaj możemy zrobić, jak podano poniżej.

    if let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell {
        // If we reached here it means the down casting was successful
    }
    else {
        // unsuccessful down casting
    }
    

    Więc zapamiętajmy to w ten sposób - jeśli ?to oznacza, że ​​nie jesteśmy pewni, czy wartość jest zerowa, czy nie (znak zapytania pojawia się, gdy nie wiemy czegoś).

  2. Porównaj to z:

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell. 
    

    Tutaj mówimy kompilatorowi, że rzutowanie w dół powinno się powieść. Jeśli to się nie powiedzie, system ulegnie awarii. Więc podajemy, !gdy jesteśmy pewni, że wartość jest różna od zera.

jishnu bala
źródło
11

Aby wyjaśnić, co powiedział Vacawama, oto przykład ...

Swift 3.0:

import UIKit

let str_value:    Any   = String("abc")!
let strOpt_value: Any?  = String("abc")!
let strOpt_nil:   Any?  = (nil as String?)
let int_value:    Any   = Int(1)
let intOpt_value: Any?  = Int(1)
let intOpt_nil:   Any?  = (nil as Int?)

// as String
//str_value     as String // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//strOpt_value  as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//strOpt_nil    as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//int_value     as String // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//intOpt_value  as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//intOpt_nil    as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?

// as? String
  str_value     as? String // == "abc"
  strOpt_value  as? String // == "abc"
  strOpt_nil    as? String // == nil
  int_value     as? String // == nil
  intOpt_value  as? String // == nil
  intOpt_nil    as? String // == nil

// as! String
  str_value     as! String // == "abc"
  strOpt_value  as! String // == "abc"
//strOpt_nil    as! String // Run-Time Error: unexpectedly found nil while unwrapping an Optional value.
//int_value     as! String // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
//intOpt_value  as! String // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
//intOpt_nil    as! String // Run-Time Error: unexpectedly found nil while unwrapping an Optional value.

// as String?
//str_value     as String? // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//strOpt_value  as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//strOpt_nil    as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//int_value     as String? // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//intOpt_value  as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//intOpt_nil    as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?

// as? String?
//str_value     as? String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  strOpt_value  as? String? // == "abc"
  strOpt_nil    as? String? // == nil
//int_value     as? String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  intOpt_value  as? String? // == nil
  intOpt_nil    as? String? // == nil

// as! String?
//str_value     as! String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  strOpt_value  as! String? // == "abc"
  strOpt_nil    as! String? // == nil
//int_value     as! String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
//intOpt_value  as! String? // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
  intOpt_nil    as! String? // == nil

// let _ = ... as String
//if let _ = str_value    as String { true } // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_value as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_nil   as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = int_value    as String { true } // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_value as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_nil   as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?

// let _ = ... as? String
if let _ = str_value    as? String { true } // true
if let _ = strOpt_value as? String { true } // true
if let _ = strOpt_nil   as? String { true } // false
if let _ = int_value    as? String { true } // false
if let _ = intOpt_value as? String { true } // false
if let _ = intOpt_nil   as? String { true } // false

// let _ = ... as! String
//if let _ = str_value    as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = strOpt_value as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = strOpt_nil   as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = int_value    as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = intOpt_value as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = intOpt_nil   as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'

// let _ = ... as String?
//if let _ = str_value    as String? { true } // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//if let _ = strOpt_value as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_nil   as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = int_value    as String? { true } // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//if let _ = intOpt_value as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_nil   as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?

// let _ = ... as? String?
//if let _ = str_value    as? String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = strOpt_value as? String? { true } // true
  if let _ = strOpt_nil   as? String? { true } // true
//if let _ = int_value    as? String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = intOpt_value as? String? { true } // false
  if let _ = intOpt_nil   as? String? { true } // true

// let _ = ... as! String?
//if let _ = str_value    as! String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = strOpt_value as! String? { true } // true
  if let _ = strOpt_nil   as! String? { true } // false
//if let _ = int_value    as! String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
//if let _ = intOpt_value as! String? { true } // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
  if let _ = intOpt_nil   as! String? { true } // false

Swift 2.0:

import UIKit

let str:    AnyObject   = String("abc")
let strOpt: AnyObject?  = String("abc")
let strNil: AnyObject?  = (nil as String?)
let int:    AnyObject   = Int(1)
let intOpt: AnyObject?  = Int(1)
let intNil: AnyObject?  = (nil as Int?)

str    as? String // == "abc"
strOpt as? String // == "abc"
strNil as? String // == nil
int    as? String // == nil
intOpt as? String // == nil
intNil as? String // == nil

str    as! String? // Compile-Time Error: Cannot downcast from 'AnyObject' to a more optional type 'String?'
strOpt as! String? // == "abc"
strNil as! String? // == nil
int    as! String? // Compile-Time Error: Cannot downcast from 'AnyObject' to a more optional type 'String?'
intOpt as! String? // Run-Time Error: Could not cast value of type '__NSCFNumber' to 'NSString'
intNil as! String? // == nil
Rozsądne
źródło
+1 za przykład, ale czy możesz mi wyjaśnić na tym samym przykładzie, aby użyć! zamiast as? podczas downcastingu jako let cell = tableView.dequeueReusableCellWithIdentifier ("Cell") as! UITableViewCell .. Zgaduję, jak? było wystarczające, dlaczego potrzeba as!
Anish Parajuli 웃
let cell = tableView.dequeueReusableCellWithIdentifier ("Cell") as? UITableViewCell. - tutaj nie wiemy, czy wynik rzutowania komórki z identyfikatorem „Cell” na UITableViewCell jest zerowy, czy nie. Jeśli nill, zwraca nill (więc unikamy awarii tutaj).
jishnu bala
interesujące, intNil as! String? // ==nilnie powoduje awarii !!! ???, ponieważ Opcjonalne <Int> .Nie różni się od Opcjonalnego <Ciągu>. Brak
onmyway133
dlaczego jesteś przybity as?do String? Dlaczego tego nie zgubisz String?? Dlaczego nie jesteś przybity as!do String?
Honey
Próba zrobienia tego placu zabaw w Swift 3, ale musisz użyć AnyzamiastAnyObject
Honey
9
  • as używany do upcastingu i odlewania typu na mostkowy
  • as? używany do bezpiecznego odlewania, zwraca zero, jeśli nie powiodło się
  • as! używany do wymuszania rzucania, awaria, jeśli się nie powiodło

Uwaga:

  • as! nie można rzutować typu surowego na opcjonalny

Przykłady:

let rawString: AnyObject = "I love swift"
let optionalString: AnyObject? = "we love swift"
let nilString: AnyObject? = (nil as String?)

let rawInt: AnyObject = Int(3)
let optionalInt: AnyObject? = Int(3)
let nilInt: AnyObject? = (nil as Int?)

Przykład

var age: Int? = nil
var height: Int? = 180

Dodając ? zaraz po typie danych informujesz kompilator, że zmienna może zawierać liczbę lub nie. Schludny! Zauważ, że tak naprawdę nie ma sensu definiowanie stałych opcjonalnych - możesz ustawić ich wartość tylko raz, dzięki czemu będziesz mógł powiedzieć, czy ich wartość będzie zerowa, czy nie.

Kiedy powinniśmy używać „?” i kiedy "!"

powiedzmy, że mamy prostą aplikację opartą na UIKit. mamy trochę kodu w naszym kontrolerze widoku i chcemy przedstawić nowy kontroler widoku na wierzchu. i musimy zdecydować o przesunięciu nowego widoku na ekran za pomocą kontrolera nawigacyjnego.

Jak wiemy, każda instancja ViewController ma kontroler nawigacji właściwości. Jeśli tworzysz aplikację opartą na kontrolerze nawigacji, ta właściwość kontrolera widoku głównego aplikacji jest ustawiana automatycznie i możesz jej używać do wypychania lub wyświetlania kontrolerów widoku. Jeśli używasz szablonu projektu pojedynczej aplikacji - nie zostanie utworzony automatycznie kontroler nawigacji, więc domyślny kontroler widoku aplikacji nie będzie zawierał niczego przechowywanego we właściwości navigationController.

Jestem pewien, że już zgadłeś, że jest to dokładnie przypadek dla opcjonalnego typu danych. Jeśli zaznaczysz UIViewController, zobaczysz, że właściwość jest zdefiniowana jako:

var navigationController: UINavigationController? { get }

Wróćmy więc do naszego przypadku użycia. Jeśli wiesz na pewno, że twój kontroler widoku zawsze będzie miał kontroler nawigacji, możesz śmiało wymusić rozpakowanie:

controller.navigationController!.pushViewController(myViewController, animated: true)

Kiedy umieścisz! za nazwą właściwości, którą powiesz kompilatorowi , nie obchodzi mnie, że ta właściwość jest opcjonalna, wiem, że gdy ten kod zostanie wykonany, zawsze będzie magazyn wartości, więc traktuj ten opcjonalny jak normalny typ danych. Czy to nie jest miłe? Co by się jednak stało, gdyby kontroler widoku nie posiadał kontrolera nawigacji? Czy sugestia, że ​​zawsze będzie przechowywana wartość w navigationController, była nieprawidłowa? Twoja aplikacja ulegnie awarii. Proste i brzydkie.

Więc użyj! tylko jeśli masz 101% pewności, że to jest bezpieczne.

A co jeśli nie masz pewności, że zawsze będzie dostępny kontroler nawigacyjny? Wtedy możesz użyć? zamiast !:

controller.navigationController?.pushViewController(myViewController, animated: true)

Co ? za nazwą właściwości mówi kompilatorowi , czy nie wiem, czy ta właściwość zawiera nil, czy wartość, więc: jeśli ma wartość, użyj jej, a poza tym po prostu rozważ całe wyrażenie nil. Skutecznie? pozwala na użycie tej właściwości tylko w przypadku, gdy istnieje kontroler nawigacji. Nie, jeśli chodzi o jakiekolwiek kontrole lub odlewy. Ta składnia jest idealna, gdy nie obchodzi Cię, czy masz kontroler nawigacji, czy nie, i chcesz coś zrobić tylko wtedy, gdy jest.

Ogromne podziękowania dla Fantageek

swiftBoy
źródło
8

Są to dwie różne formy Downcasting w Swift.

( as?) , który jest formą warunkową , zwraca opcjonalną wartość typu, do którego próbujesz obniżyć.

Możesz go użyć, gdy nie jesteś pewien, czy upadek się powiedzie. Ta forma operatora zawsze zwraca wartość opcjonalną, a wartość będzie równa zero, jeśli downcast nie był możliwy. Umożliwia to sprawdzenie pomyślnego obniżenia.


( as!) , o którym wiadomo, że jest formą wymuszoną , próbuje powalić i wymusić rozpakowanie wyniku jako pojedynczej złożonej akcji.

Powinieneś go używać TYLKO wtedy, gdy masz pewność, że upadek zawsze się powiedzie. Ta forma operatora spowoduje błąd w czasie wykonywania, jeśli spróbujesz obniżyć do niepoprawnego typu klasy.

Aby uzyskać więcej informacji, zapoznaj się z sekcją dotyczącą rzutowania typów w dokumentacji firmy Apple.

Scott Zhu
źródło
4

Może ten przykład kodu pomoże komuś zrozumieć zasadę:

var dict = [Int:Any]()
dict[1] = 15

let x = dict[1] as? String
print(x) // nil because dict[1] is an Int

dict[2] = "Yo"

let z = dict[2] as! String?
print(z) // optional("Yo")
let zz = dict[1] as! String // crashes because a forced downcast fails


let m = dict[3] as! String?
print(m) // nil. the forced downcast succeeds, but dict[3] has no value
smileBot
źródło
Niech z2 = dict [2] jako! Ciąg // „Yo” (nie opcjonalny)
Jay
0

Najłatwiej będzie zapamiętać wzorzec dla tych operatorów w języku Swift jako: !implikuje „to może być pułapką”, podczas gdy? wskazuje „to może być zero”.

patrz: https://developer.apple.com/swift/blog/?id=23

Zgpeace
źródło
-1

Jestem nowicjuszem w Swift i piszę ten przykład, próbując wyjaśnić, jak rozumiem, „opcje”. Jeśli się mylę, popraw mnie.

Dzięki.


class Optional {

    var lName:AnyObject! = "1"

    var lastName:String!
}

let obj = Optional()

print(obj.lName)

print(obj.lName!)

obj.lastName = obj.lName as? String

print(obj.lastName)

(1): obj.lastName = obj.lName as! String

vs

(2): obj.lastName = obj.lName as? String

Odp: (1) Tutaj programista jest pewny, że “obj.lName”zawiera obiekt typu string. Więc po prostu nadaj tę wartość “obj.lastName”.

Teraz, jeśli programista ma rację, oznacza "obj.lName"obiekt typu string, to nie ma problemu. „obj.lastName” przyjmie tę samą wartość.

Ale jeśli programista się myli, oznacza "obj.lName"to, że nie jest obiektem typu string, tj. Zawiera jakiś inny obiekt typu, jak "NSNumber" itp. Następnie CRASH (Run Time Error).

(2) Programista nie jest pewien, czy “obj.lName”zawiera obiekt typu string lub jakikolwiek inny obiekt typu. Więc ustaw tę wartość na, “obj.lastName”jeśli jest to łańcuch.

Teraz, jeśli programista ma rację, oznacza “obj.lName”obiekt typu string, to nie ma problemu.“obj.lastName”ustawi się na tę samą wartość.

Ale jeśli programista się myli, oznacza to, że obiekt obj.lName nie jest obiektem typu string, tj. Zawiera jakiś inny obiekt typu, np. "NSNumber"Itp. Następnie “obj.lastName”ustawi wartość nil. Więc, bez wypadku (szczęśliwy :)

iPhoneBuddy
źródło