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?
swift
optional
downcast
optional-variables
sdduursma
źródło
źródło
Odpowiedzi:
Praktyczna różnica jest taka:
var optionalString = dict["SomeKey"] as? String
optionalString
będzie zmienną typuString?
. Jeśli typ bazowy jest inny niż a,String
to zostanie po prostu przypisanynil
do opcjonalnego.var optionalString = dict["SomeKey"] as! String?
To mówi, wiem , że to jest
String?
. To również spowodujeoptionalString
bycie typuString?
, ale ulegnie awarii, jeśli typ bazowy jest inny.Pierwszy styl jest następnie używany z,
if let
aby 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) }
źródło
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
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ś).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.źródło
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
źródło
intNil as! String? // ==nil
nie powoduje awarii !!! ???, ponieważ Opcjonalne <Int> .Nie różni się od Opcjonalnego <Ciągu>. Brakas?
doString
? Dlaczego tego nie zgubiszString?
? Dlaczego nie jesteś przybityas!
doString
?Any
zamiastAnyObject
as
używany do upcastingu i odlewania typu na mostkowyas?
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łoUwaga:
as!
nie można rzutować typu surowego na opcjonalnyPrzykł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.
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
źródło
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ć.(
as!
) , o którym wiadomo, że jest formą wymuszoną , próbuje powalić i wymusić rozpakowanie wyniku jako pojedynczej złożonej akcji.Aby uzyskać więcej informacji, zapoznaj się z sekcją dotyczącą rzutowania typów w dokumentacji firmy Apple.
źródło
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
źródło
Pierwszym jest „rzutowanie warunkowe” (zajrzyj do sekcji „operatory rzutowania typów” w dokumentacji, którą linkowałem) . Jeśli rzutowanie powiedzie się, wartość wyrażenia jest zawijana jako opcjonalna i zwracana, w przeciwnym razie zwracana wartość wynosi zero.
Drugi oznacza, że optionalString może być obiektem typu string lub może mieć wartość nil.
Więcej informacji można znaleźć w tym pokrewnym pytaniu .
źródło
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
źródło
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 :)źródło