Downcasting optionnels dans Swift: comme? Tapez, ou comme! Type?

95

Compte tenu de ce qui suit dans Swift:

var optionalString: String?
let dict = NSDictionary()

Quelle est la différence pratique entre les deux énoncés suivants:

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

contre

optionalString = dict.objectForKey("SomeKey") as! String?
sdduursma
la source

Réponses:

142

La différence pratique est la suivante:

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

optionalStringsera une variable de type String?. Si le type sous-jacent est autre chose qu'un, Stringcela sera simplement assigné nilà l'option.

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

Cela dit, je sais que cette chose est un String?. Cela entraînera également optionalStringun type String?, mais il plantera si le type sous-jacent est autre chose.

Le premier style est ensuite utilisé avec if letpour déballer en toute sécurité l'option:

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
la source
La première méthode n'est-elle pas toujours meilleure alors? Les deux renvoient un optionnel de type String? Il semble que la deuxième méthode fasse la même chose que la première mais pourrait planter si le downcast échoue. Alors pourquoi l'utiliser?
Sikander
6
Oui @Sikander, le premier est toujours meilleur. Je n'utiliserais jamais le second.
vacawama
14

as? Types- signifie que le processus de coulée vers le bas est facultatif. Le processus peut réussir ou non (le système retournera nil si la conversion descendante échoue).

as! Type?- Ici, le processus de down casting devrait réussir ( !indique que). Le point d'interrogation de fin indique si le résultat final peut être nul ou non.

Plus d'informations concernant "!" et "?"

Prenons 2 cas

  1. Considérer:

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

    Ici, nous ne savons pas si le résultat de la conversion descendante de la cellule avec l'identificateur "Cell" vers UITableViewCell est un succès ou non. En cas d'échec, il retourne nil (nous évitons donc les plantages ici). Ici, nous pouvons faire comme indiqué ci-dessous.

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

    Alors rappelons-nous-en comme ceci - Si ?cela signifie que nous ne savons pas si la valeur est nulle ou non (le point d'interrogation vient quand nous ne savons pas des choses).

  2. Comparez cela à:

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

    Ici, nous disons au compilateur que la conversion descendante devrait réussir. En cas d'échec, le système plantera. Nous donnons donc !quand nous sommes sûrs que la valeur est non nulle.

jishnu bala
la source
11

Pour clarifier ce que vacawama a dit, voici un exemple ...

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
Sensé
la source
+1 pour votre exemple mais pouvez-vous m'expliquer avec le même exemple à utiliser que! à la place de as? lors du downcasting comme let cell = tableView.dequeueReusableCellWithIdentifier ("Cell") as! UITableViewCell..je devine que? était suffisant pourquoi était le besoin de as!
Anish Parajuli 웃
laissez cell = tableView.dequeueReusableCellWithIdentifier ("Cell") comme? UITableViewCell. - ici, nous ne savons pas si le résultat de la conversion descendante de la cellule avec l'identifiant "Cell" vers UITableViewCell est nul ou non. Si nul, alors il retourne nill (nous évitons donc le crash ici).
jishnu bala
intéressant, intNil as! String? // ==nilne provoque pas de plantage !!! ???, comme facultatif <Int> .Aucun n'est différent de facultatif <String> .Aucun
onmyway133
pourquoi avez - vous baissés as?à String? Pourquoi ne pas le rabaisser String?? Pourquoi ne pas baissés as!à String?
Honey
Tentative de faire ce terrain de jeu dans Swift 3, mais vous devez utiliser à la Anyplace deAnyObject
Chérie
9
  • as utilisé pour la projection ascendante et le moulage de type en type ponté
  • as? utilisé pour un casting sécurisé, retourne zéro en cas d'échec
  • as! utilisé pour forcer le casting, crash en cas d'échec

Remarque:

  • as! impossible de convertir le type brut en facultatif

Exemples:

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

Exemple

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

En ajoutant un ? immédiatement après le type de données, vous indiquez au compilateur que la variable peut contenir un nombre ou non. Soigné! Notez que cela n'a pas vraiment de sens de définir des constantes optionnelles - vous ne pouvez définir leur valeur qu'une seule fois et vous pourrez donc dire si leur valeur sera nulle ou non.

Quand devrions-nous utiliser "?" et quand "!"

disons que nous avons une application simple basée sur UIKit. nous avons du code dans notre contrôleur de vue et souhaitons y présenter un nouveau contrôleur de vue. et nous devons décider de pousser la nouvelle vue à l'écran à l'aide du contrôleur de navigation.

Comme nous le savons, chaque instance de ViewController possède un contrôleur de navigation de propriété. Si vous créez une application basée sur un contrôleur de navigation, cette propriété du contrôleur de vue principale de votre application est définie automatiquement et vous pouvez l'utiliser pour pousser ou afficher des contrôleurs de vue. Si vous utilisez un seul modèle de projet d'application, il n'y aura pas de contrôleur de navigation créé automatiquement pour vous, donc le contrôleur de vue par défaut de votre application n'aura rien stocké dans la propriété navigationController.

Je suis sûr que vous avez déjà deviné qu'il s'agit exactement d'un cas pour un type de données facultatif. Si vous cochez UIViewController, vous verrez que la propriété est définie comme:

var navigationController: UINavigationController? { get }

Revenons donc à notre cas d'utilisation. Si vous savez avec certitude que votre contrôleur de vue aura toujours un contrôleur de navigation, vous pouvez continuer et forcer le déballage:

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

Lorsque vous mettez un! derrière le nom de la propriété, vous dites au compilateur que je me fiche que cette propriété soit facultative, je sais que lorsque ce code s'exécute, il y aura toujours un magasin de valeurs, alors traitez cette option comme un type de données normal. N'est-ce pas sympa? Que se passerait-il s'il n'y avait pas de contrôleur de navigation pour votre contrôleur de vue? Si vous suggérez qu'il y aura toujours une valeur stockée dans navigationController était faux? Votre application va planter. Simple et moche comme ça.

Alors, utilisez! seulement si vous êtes sûr à 101% que c'est sûr.

Et si vous n'êtes pas sûr qu'il y aura toujours un contrôleur de navigation? Ensuite, vous pouvez utiliser? à la place d'un !:

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

Qu'est-ce que c'est? derrière le nom de la propriété indique au compilateur que je ne sais pas si cette propriété contient nil ou une valeur, donc: si elle a une valeur, utilisez-la, et sinon considérez simplement l'expression entière comme nulle. Effectivement le? vous permet d'utiliser cette propriété juste au cas où il y aurait un contrôleur de navigation. Non s'il s'agit de chèques de toute nature ou de pièces moulées de toute sorte. Cette syntaxe est parfaite lorsque vous ne vous souciez pas de savoir si vous avez un contrôleur de navigation ou non, et que vous ne voulez faire quelque chose que s'il y en a.

Un grand merci à Fantageek

swiftBoy
la source
8

Ce sont deux formes différentes de Downcasting dans Swift.

( as?) , qui est connue pour être la forme conditionnelle , renvoie une valeur facultative du type vers lequel vous essayez de réduire.

Vous pouvez l'utiliser lorsque vous n'êtes pas sûr que le downcast réussira. Cette forme de l'opérateur renverra toujours une valeur facultative, et la valeur sera nulle si le downcast n'était pas possible. Cela vous permet de vérifier un downcast réussi.


( as!) , qui est connue pour être la forme forcée , tente le downcast et force le résultat en une seule action composée.

Vous devez l'utiliser UNIQUEMENT lorsque vous êtes sûr que le downcast réussira toujours. Cette forme de l'opérateur déclenchera une erreur d'exécution si vous essayez de réduire à un type de classe incorrect.

Pour plus de détails, veuillez consulter la section Type Casting de la documentation Apple.

Scott Zhu
la source
4

Peut-être que cet exemple de code aidera quelqu'un à comprendre le principe:

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
la source
Aussi, laissez z2 = dict [2] comme! Chaîne // "Yo" (non facultatif)
Jay
0

Il peut être plus facile de se souvenir du modèle de ces opérateurs dans Swift comme: !implique «cela pourrait piéger», tandis que ?indique «cela pourrait être nul».

se référer à: https://developer.apple.com/swift/blog/?id=23

Zgpeace
la source
-1

Je suis novice en Swift et j'écris cet exemple en essayant d'expliquer ce que je comprends des «optionnels». Si je me trompe, veuillez me corriger.

Merci.


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

contre

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

Ans: (1) Ici, le programmeur est sûr qu'il “obj.lName”contient un objet de type chaîne. Alors donnez simplement cette valeur à “obj.lastName”.

Maintenant, si le programmeur est correct, il "obj.lName"s'agit d'un objet de type chaîne, alors pas de problème. "obj.lastName" sera défini sur la même valeur.

Mais si le programmeur se trompe, cela signifie qu'il "obj.lName"n'est pas un objet de type chaîne, c'est-à-dire qu'il contient un autre objet de type comme "NSNumber", etc. Puis CRASH (Run Time Error).

(2) Le programmeur n'est pas sûr que “obj.lName”contient un objet de type chaîne ou tout autre objet de type. Définissez donc cette valeur sur “obj.lastName”s'il s'agit de type chaîne.

Maintenant, si le programmeur est correct, il “obj.lName”s'agit d'un objet de type chaîne, alors pas de problème. “obj.lastName”sera mis à la même valeur.

Mais si le programmeur se trompe, cela signifie que obj.lName n'est pas un objet de type chaîne, c'est-à-dire qu'il contient un autre type d'objet comme "NSNumber"etc. Ensuite “obj.lastName”sera mis à la valeur nil. Donc, pas de crash (heureux :)

iPhoneBuddy
la source