Opzioni di downcasting in Swift: come? Scrivi o come! Genere?


95

Dato quanto segue in Swift:

var optionalString: String?
let dict = NSDictionary()

Qual è la differenza pratica tra le seguenti due affermazioni:

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

vs

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

Risposte:


142

La differenza pratica è questa:

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

optionalStringsarà una variabile di tipo String?. Se il tipo sottostante è qualcosa di diverso da a, Stringquesto verrà semplicemente assegnato nilin modo innocuo all'opzionale.

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

Questo dice, so che questa cosa è un file String?. Anche questo risulterà optionalStringessere di tipo String?, ma andrà in crash se il tipo sottostante è qualcos'altro.

Il primo stile viene quindi utilizzato if letper scartare in sicurezza gli optional:

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

Allora il primo metodo non è sempre migliore? Entrambi restituiscono un opzionale di tipo String? Sembra che il secondo metodo faccia la stessa cosa del primo ma potrebbe bloccarsi se il downcast non ha successo. Allora perché usarlo?
Sikander

6
Sì @Sikander, il primo è sempre meglio. Non userei mai il secondo.
vacawama

14

as? Types- significa che il processo di down casting è opzionale. Il processo può avere successo o meno (il sistema restituirà zero se il down casting fallisce). In qualsiasi modo non andrà in crash se il down casting fallisce.

as! Type?- Qui il processo di down casting dovrebbe avere successo ( !indica questo). Il punto interrogativo finale indica se il risultato finale può essere nullo o meno.

Maggiori informazioni su "!" e "?"

Prendiamo 2 casi

  1. Tener conto di:

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

    Qui non sappiamo se il risultato del down casting della cella con identificatore "Cell" in UITableViewCell sia o meno successo. Se non ha successo, restituisce zero (quindi evitiamo il crash qui). Qui possiamo fare come indicato di seguito.

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

    Quindi ricordiamolo in questo modo: se ?significa che non siamo sicuri che il valore sia nullo o meno (il punto interrogativo viene quando non sappiamo le cose).

  2. Confrontalo con:

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

    Qui diciamo al compilatore che il down casting dovrebbe avere successo. Se fallisce, il sistema andrà in crash. Quindi diamo !quando siamo sicuri che il valore non è nullo.


11

Per chiarire cosa ha detto vacawama, ecco un esempio ...

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

+1 per il tuo esempio ma puoi spiegarmi con lo stesso esempio da usare come! al posto di come? durante il downcasting come let cell = tableView.dequeueReusableCellWithIdentifier ("Cell") as! UITableViewCell .. immagino come? era sufficiente perché c'era bisogno di come!
Anish Parajuli 웃

let cell = tableView.dequeueReusableCellWithIdentifier ("Cell") come? UITableViewCell. - qui non sappiamo se il risultato del down casting della cella con identificatore "Cell" a UITableViewCell sia nullo o meno. Se nill, restituisce nill (quindi evitiamo il crash qui).
jishnu bala

interessante, intNil as! String? // ==nilnon causa un arresto anomalo !!! ???, poiché facoltativo <Int>. Nessuno è diverso da facoltativo <String>. Nessuno
onmyway133

perché lo fai abbattuta as?a String? Perché non lo abbassi String?? Perché non ti abbatti as!a String?
Honey

Tentativo di fare questo playground in Swift 3, ma devi usare Anyinvece diAnyObject
Honey

9
  • as utilizzato per l'upcasting e il casting del tipo su un tipo a ponte
  • as? utilizzato per il casting sicuro, restituire zero se fallito
  • as! usato per forzare il casting, crash se fallito

Nota:

  • as! non è possibile eseguire il cast del tipo raw su opzionale

Esempi:

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

Esempio

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

Aggiungendo un ? subito dopo il tipo di dati si dice al compilatore che la variabile potrebbe contenere un numero o meno. Neat! Nota che non ha davvero senso definire costanti opzionali: puoi impostare il loro valore solo una volta e quindi potresti dire se il loro valore sarà nullo o meno.

Quando dovremmo usare "?" e quando "!"

diciamo che abbiamo un'app semplice basata su UIKit. abbiamo del codice nel nostro controller di visualizzazione e vogliamo presentarvi un nuovo controller di visualizzazione. e dobbiamo decidere di spingere la nuova visualizzazione sullo schermo utilizzando il controller di navigazione.

Come sappiamo, ogni istanza di ViewController ha un controller di navigazione delle proprietà. Se stai creando un'app basata sul controller di navigazione, questa proprietà del controller della vista principale della tua app viene impostata automaticamente e puoi usarla per spingere o far apparire i controller di visualizzazione. Se usi un singolo modello di progetto di app, non ci sarà un controller di navigazione creato automaticamente per te, quindi il controller di visualizzazione predefinito della tua app non avrà nulla archiviato nella proprietà navigationController.

Sono sicuro che hai già indovinato che questo è esattamente il caso di un tipo di dati opzionale. Se controlli UIViewController vedrai che la proprietà è definita come:

var navigationController: UINavigationController? { get }

Quindi torniamo al nostro caso d'uso. Se sai per certo che il tuo controller di visualizzazione avrà sempre un controller di navigazione, puoi andare avanti e forzare lo scartamento:

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

Quando metti un! dietro il nome della proprietà dici al compilatore che non mi interessa che questa proprietà sia opzionale, so che quando questo codice viene eseguito ci sarà sempre un archivio di valori, quindi tratta questo Opzionale come un normale tipo di dati. Beh, non è carino? Cosa succederebbe se non ci fosse un controller di navigazione per il tuo controller di visualizzazione? Se suggerisci che ci sarà sempre un valore memorizzato in navigationController era sbagliato? La tua app andrà in crash. Semplice e brutto come quello.

Quindi, usa! solo se sei sicuro al 101% che sia sicuro.

Che ne dici se non sei sicuro che ci sarà sempre un controller di navigazione? Quindi puoi usare? invece di!:

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

Cosa? dietro il nome della proprietà dice al compilatore che non so se questa proprietà contiene zero o un valore, quindi: se ha valore usalo, e altrimenti considera l'intera espressione zero. Effettivamente il? ti consente di utilizzare quella proprietà solo nel caso in cui sia presente un controller di navigazione. No se assegni di qualsiasi tipo o fusioni di qualsiasi tipo. Questa sintassi è perfetta quando non ti interessa se hai o meno un controller di navigazione e vuoi fare qualcosa solo se c'è.

Un enorme grazie a Fantageek


8

Sono due diverse forme di Downcasting in Swift.

( as?) , noto per essere il modulo condizionale , restituisce un valore facoltativo del tipo a cui si sta tentando di eseguire il downcast.

Puoi usarlo quando non sei sicuro che il downcast avrà successo. Questa forma dell'operatore restituirà sempre un valore opzionale e il valore sarà nullo se il downcast non è stato possibile. Ciò consente di verificare la riuscita del downcast.


( as!) , che è nota per essere la Forma Forzata , tenta di abbattere e scartare forzatamente il risultato come un'unica azione composta.

Dovresti usarlo SOLO quando sei sicuro che il downcast avrà sempre successo. Questa forma dell'operatore attiverà un errore di runtime se si tenta di eseguire il downcast a un tipo di classe errato.

Per maggiori dettagli, consulta la sezione Type Casting della documentazione di Apple.


4

Forse questo esempio di codice aiuterà qualcuno a capire il principio:

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

Inoltre, sia z2 = dict [2] as! String // "Yo" (non facoltativo)
Jay



-1

Sono alle prime armi con Swift e sto scrivendo questo esempio cercando di spiegare come capisco sugli "optionals". Se sbaglio, correggimi.

Grazie.


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

Risposta: (1) Qui il programmatore è sicuro che “obj.lName”contiene un oggetto di tipo stringa. Quindi dai quel valore a “obj.lastName”.

Ora, se il programmatore è corretto significa che "obj.lName"è un oggetto di tipo stringa, quindi nessun problema. "obj.lastName" verrà impostato sullo stesso valore.

Ma se il programmatore è sbagliato significa che "obj.lName"non è un oggetto di tipo stringa, cioè contiene qualche altro oggetto di tipo come "NSNumber" ecc. Poi CRASH (Run Time Error).

(2) Il programmatore non è sicuro che “obj.lName”contenga un oggetto di tipo stringa o qualsiasi altro oggetto di tipo. Quindi imposta quel valore su “obj.lastName”se è di tipo stringa.

Ora, se il programmatore è corretto significa che “obj.lName”è un oggetto di tipo stringa, quindi nessun problema. “obj.lastName”verrà impostato sullo stesso valore.

Ma se il programmatore è sbagliato significa che obj.lName non è un oggetto di tipo stringa, cioè contiene qualche altro oggetto di tipo come "NSNumber"ecc. Quindi “obj.lastName”verrà impostato sul valore nullo. Quindi, nessun incidente (felice :)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.