Downcasting-Optionen in Swift: as? Typ oder als! Art?

95

Angesichts der folgenden in Swift:

var optionalString: String?
let dict = NSDictionary()

Was ist der praktische Unterschied zwischen den folgenden zwei Aussagen:

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

vs.

optionalString = dict.objectForKey("SomeKey") as! String?
sdduursma
quelle
1
Aslo See The as! Betreiber von Apple
Honey

Antworten:

142

Der praktische Unterschied ist folgender:

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

optionalStringwird eine Variable vom Typ sein String?. Wenn der zugrunde liegende Typ etwas anderes als ein ist, wird Stringdies harmlos nur nildem optionalen Typ zugewiesen .

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

Das heißt, ich weiß , dass dieses Ding ein ist String?. Auch dies führt in optionalStringder Art zu sein String?, aber es wird abstürzen , wenn der zugrunde liegende Typ etwas anderes.

Der erste Stil wird dann verwendet if let, um das optionale Element sicher auszupacken:

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
quelle
Ist die erste Methode dann nicht immer besser? Beide geben eine Option vom Typ String zurück? Es scheint, als ob die zweite Methode dasselbe tut wie die erste, aber möglicherweise abstürzt, wenn der Downcast nicht erfolgreich ist. Warum also überhaupt verwenden?
Sikander
6
Ja @Sikander, der erste ist immer besser. Ich würde niemals die zweite verwenden.
Vacawama
14

as? Types- bedeutet, dass der Down Casting-Prozess optional ist. Der Prozess kann erfolgreich sein oder nicht (das System gibt null zurück, wenn das Downcasting fehlschlägt). Auf keinen Fall stürzt es ab, wenn das Downcasting fehlschlägt.

as! Type?- Hier sollte der Downcasting-Prozess erfolgreich sein ( !zeigt dies an). Das abschließende Fragezeichen gibt an, ob das Endergebnis Null sein kann oder nicht.

Weitere Infos zu "!" und "?"

Nehmen wir 2 Fälle

  1. Erwägen:

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

    Hier wissen wir nicht, ob das Ergebnis des Herunterwerfens einer Zelle mit der Kennung "Zelle" auf UITableViewCell erfolgreich ist oder nicht. Wenn dies nicht erfolgreich ist, wird null zurückgegeben (daher vermeiden wir hier einen Absturz). Hier können wir wie unten angegeben vorgehen.

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

    Erinnern wir uns also daran - Wenn ?es bedeutet, dass wir nicht sicher sind, ob der Wert Null ist oder nicht (Fragezeichen kommt, wenn wir nichts wissen).

  2. Vergleichen Sie das mit:

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

    Hier sagen wir dem Compiler, dass Downcasting erfolgreich sein sollte. Wenn dies fehlschlägt, stürzt das System ab. Wir geben also, !wenn wir sicher sind, dass der Wert nicht Null ist.

Jishnu Bala
quelle
11

Um zu verdeutlichen, was Vacawama gesagt hat, hier ein Beispiel ...

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
Sinnvoll
quelle
+1 für dein Beispiel, aber kannst du mir das gleiche Beispiel erklären wie! anstelle von as? während des Downcasts als let cell = tableView.dequeueReusableCellWithIdentifier ("Cell") as! UITableViewCell..Ich rate als? war ausreichend warum war die Notwendigkeit von as!
Anish Parajuli 웃
Lassen Sie cell = tableView.dequeueReusableCellWithIdentifier ("Cell") als? UITableViewCell. - Hier wissen wir nicht, ob das Ergebnis des Herunterwerfens einer Zelle mit der Kennung "Cell" auf UITableViewCell gleich Null ist oder nicht. Wenn nill, wird nill zurückgegeben (daher vermeiden wir hier einen Absturz).
Jishnu Bala
interessant, intNil as! String? // ==nilverursacht keinen Absturz !!! ???, da Optional <Int> .None unterscheidet sich von Optional <String> .None
onmyway133
warum tun Sie gesenkten as?zu String? Warum lässt du es nicht nieder String?? Warum gehst du nicht niedergeschlagen as!zu String?
Honig
Der Versuch, diesen Spielplatz in Swift 3 zu machen, muss aber Anystattdessen verwendet werdenAnyObject
Honey
9
  • as Wird für Upcasting und Typguss auf überbrückten Typ verwendet
  • as? Wird für sicheres Gießen verwendet. Wenn dies fehlschlägt, wird Null zurückgegeben
  • as! wird verwendet, um das Casting zu erzwingen, stürzt ab, wenn dies fehlschlägt

Hinweis:

  • as! Der Rohtyp kann nicht in optional umgewandelt werden

Beispiele:

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?)

Beispiel

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

Durch Hinzufügen eines ? Unmittelbar nach dem Datentyp teilen Sie dem Compiler mit, dass die Variable möglicherweise eine Zahl enthält oder nicht. Ordentlich! Beachten Sie, dass es nicht wirklich sinnvoll ist, optionale Konstanten zu definieren. Sie können ihren Wert nur einmal festlegen und können daher sagen, ob ihr Wert Null ist oder nicht.

Wann sollten wir "?" und wann "!"

Nehmen wir an, wir haben eine UIKit-basierte einfache App. Wir haben Code in unserem View Controller und möchten einen neuen View Controller darüber präsentieren. und wir müssen uns entscheiden, die neue Ansicht mit dem Navigationscontroller auf dem Bildschirm anzuzeigen.

Wie wir wissen, verfügt jede ViewController-Instanz über einen Eigenschaftsnavigationscontroller. Wenn Sie eine auf Navigationscontrollern basierende App erstellen, wird diese Eigenschaft des Master View Controllers Ihrer App automatisch festgelegt und Sie können sie zum Push- oder Popup von View Controllern verwenden. Wenn Sie eine einzelne App-Projektvorlage verwenden, wird kein Navigations-Controller automatisch für Sie erstellt, sodass im Standard-Ansichts-Controller Ihrer App nichts in der Eigenschaft navigationController gespeichert ist.

Ich bin sicher, Sie haben bereits vermutet, dass dies genau der Fall für einen optionalen Datentyp ist. Wenn Sie UIViewController aktivieren, sehen Sie, dass die Eigenschaft wie folgt definiert ist:

var navigationController: UINavigationController? { get }

Kehren wir also zu unserem Anwendungsfall zurück. Wenn Sie sicher sind, dass Ihr View Controller immer über einen Navigationscontroller verfügt, können Sie das Entpacken erzwingen:

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

Wenn Sie eine setzen! Hinter dem Eigenschaftsnamen teilen Sie dem Compiler mit, dass es mir egal ist, dass diese Eigenschaft optional ist. Ich weiß, dass bei der Ausführung dieses Codes immer ein Wertspeicher vorhanden ist. Behandeln Sie diese Option daher wie einen normalen Datentyp. Na ist das nicht schön Was würde jedoch passieren, wenn Ihr Ansichtscontroller keinen Navigationscontroller enthält? Wenn Sie vorschlagen, dass immer ein Wert in navigationController gespeichert wird, war das falsch? Ihre App wird abstürzen. So einfach und hässlich.

Also, benutze! Nur wenn Sie zu 101% sicher sind, dass dies sicher ist.

Wie wäre es, wenn Sie nicht sicher sind, ob es immer einen Navigationscontroller geben wird? Dann können Sie verwenden? anstelle einer !:

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

Was zum ? Hinter dem Eigenschaftsnamen steht, dass der Compiler weiß, ob diese Eigenschaft nil oder einen Wert enthält. Wenn sie also einen Wert hat, verwenden Sie ihn und betrachten Sie im Übrigen einfach den gesamten Ausdruck nil. Effektiv die? Mit dieser Eigenschaft können Sie diese Eigenschaft nur für den Fall verwenden, dass ein Navigationscontroller vorhanden ist. Nein, wenn Schecks jeglicher Art oder Gussteile jeglicher Art. Diese Syntax ist perfekt, wenn Sie sich nicht darum kümmern, ob Sie einen Navigationscontroller haben oder nicht, und nur dann etwas tun möchten, wenn es einen gibt.

Vielen Dank an Fantageek

swiftBoy
quelle
8

Es sind zwei verschiedene Formen von Downcasting in Swift.

( as?) , von dem bekannt ist, dass es sich um das bedingte Formular handelt , gibt einen optionalen Wert des Typs zurück, auf den Sie einen Downcast durchführen möchten.

Sie können es verwenden, wenn Sie nicht sicher sind, ob der Downcast erfolgreich sein wird. Diese Form des Operators gibt immer einen optionalen Wert zurück, und der Wert ist Null, wenn der Downcast nicht möglich war. Auf diese Weise können Sie nach einem erfolgreichen Downcast suchen.


( as!) , von dem bekannt ist, dass es sich um die erzwungene Form handelt , versucht den Downcast und packt das Ergebnis als einzelne zusammengesetzte Aktion aus.

Sie sollten es NUR verwenden, wenn Sie sicher sind, dass der Downcast immer erfolgreich sein wird. Diese Form des Operators löst einen Laufzeitfehler aus, wenn Sie versuchen, auf einen falschen Klassentyp herunterzuspielen.

Weitere Informationen finden Sie im Abschnitt Type Casting in der Apple-Dokumentation.

Scott Zhu
quelle
4

Vielleicht hilft dieses Codebeispiel jemandem, das Prinzip zu verstehen:

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
quelle
Lassen Sie auch z2 = diktieren [2] als! String // "Yo" (nicht optional)
Jay
0

Es ist möglicherweise am einfachsten, sich das Muster für diese Operatoren in Swift zu merken, da: !impliziert, dass "dies möglicherweise eine Falle ist", während? anzeigt, dass dies möglicherweise null ist.

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

Zgpeace
quelle
-1

Ich bin ein Neuling in Swift und schreibe dieses Beispiel, um zu erklären, wie ich 'Optionals' verstehe. Wenn ich falsch liege, korrigiere mich bitte.

Vielen Dank.


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

Antwort: (1) Hier ist der Programmierer verdammt sicher, dass er ein “obj.lName”Objekt vom Typ String enthält. Geben Sie einfach diesen Wert an “obj.lastName”.

Wenn der Programmierer korrekt ist, bedeutet dies, dass es sich um ein "obj.lName"Objekt vom Typ Zeichenfolge handelt, ist dies kein Problem. "obj.lastName" wird auf den gleichen Wert gesetzt.

Wenn der Programmierer jedoch falsch ist, bedeutet dies, dass "obj.lName"es sich nicht um ein Objekt vom Typ Zeichenfolge handelt, dh es enthält ein anderes Objekt vom Typ "NSNumber" usw. Dann CRASH (Run Time Error).

(2) Der Programmierer ist sich nicht sicher, ob er ein “obj.lName”Objekt vom Typ Zeichenfolge oder ein anderes Objekt vom Typ enthält. Setzen Sie diesen Wert also auf, “obj.lastName”wenn es sich um einen Zeichenfolgentyp handelt.

Wenn der Programmierer korrekt ist, bedeutet dies, dass es sich um ein “obj.lName”Objekt vom Typ Zeichenfolge handelt, ist dies kein Problem.“obj.lastName”wird auf den gleichen Wert gesetzt.

Wenn der Programmierer jedoch falsch ist, bedeutet dies, dass obj.lName kein Objekt vom Typ Zeichenfolge ist, dh ein anderes Objekt vom Typ "NSNumber"usw. enthält . Dann “obj.lastName”wird der Wert auf Null gesetzt. Also, kein Absturz (Happy :)

iPhoneBuddy
quelle