Opcionales Downcasting en Swift: ¿como? Escriba, o como! ¿Tipo?

95

Dado lo siguiente en Swift:

var optionalString: String?
let dict = NSDictionary()

¿Cuál es la diferencia práctica entre las siguientes dos declaraciones:

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

vs

optionalString = dict.objectForKey("SomeKey") as! String?
sdduursma
fuente
1
¡Vea el como! Operador de Apple
Cariño

Respuestas:

142

La diferencia práctica es esta:

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

optionalStringserá una variable de tipo String?. Si el tipo subyacente es diferente a Stringesto, esto se asignará sin causar daño nilal opcional.

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

Esto dice, sé que esto es un String?. Esto también resultará en optionalStringser de tipo String?, pero fallará si el tipo subyacente es otro.

El primer estilo se usa luego con if letpara desenvolver de manera segura el opcional:

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
fuente
¿No es siempre mejor el primer método? Ambos devuelven un opcional de tipo String? Parece que el segundo método hace lo mismo que el primero, pero podría fallar si el abatido no tiene éxito. Entonces, ¿por qué usarlo?
Sikander
6
Sí @Sikander, el primero siempre es mejor. Nunca usaría el segundo.
vacawama
14

as? Types- significa que el proceso de fundición hacia abajo es opcional. El proceso puede ser exitoso o no (el sistema devolverá nil si falla el down casting). De ninguna manera fallará si falla el down casting.

as! Type? - Aquí el proceso de down casting debería ser exitoso (! indica que). El signo de interrogación final indica si el resultado final puede ser nulo o no.

Más información sobre "!" y "?"

Tomemos 2 casos

  1. Considerar:

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

    Aquí no sabemos si el resultado de la conversión descendente de la celda con el identificador "Celda" a UITableViewCell es un éxito o no. Si no tiene éxito, devuelve nil (por lo que evitamos el bloqueo aquí). Aquí podemos hacer lo que se indica a continuación.

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

    Así que recordémoslo así: si ?eso significa que no estamos seguros de si el valor es nulo o no (el signo de interrogación aparece cuando no sabemos cosas).

  2. Contraste eso con:

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

    Aquí le decimos al compilador que la conversión descendente debería ser exitosa. Si falla, el sistema se bloqueará. Entonces damos !cuando estamos seguros de que el valor no es nulo.

jishnu bala
fuente
11

Para aclarar lo que dijo vacawama, aquí hay un ejemplo ...

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
Sentido
fuente
+1 para su ejemplo, pero ¿puede explicarme con el mismo ejemplo para usar como! en lugar de como? mientras se desciende como let cell = tableView.dequeueReusableCellWithIdentifier ("Cell") como! UITableViewCell ... supongo que? era suficiente por qué era la necesidad de como!
Anish Parajuli 웃
let cell = tableView.dequeueReusableCellWithIdentifier ("Cell") como? UITableViewCell. - aquí no sabemos si el resultado de la conversión descendente de la celda con el identificador "Celda" a UITableViewCell es nulo o no. Si es nill, devuelve nill (por lo que aquí evitamos el bloqueo).
jishnu bala
interesante, intNil as! String? // ==nilno causa un bloqueo !!! ???, como <Int> opcional. Ninguno es diferente de <String> opcional
Ninguno
¿por qué te abates as?a String? ¿Por qué no lo abatiste String?? ¿Por qué no abatido as!a String?
Cariño
Intentando hacer este patio de recreo en Swift 3, pero tienes que usar en Anylugar deAnyObject
Cariño
9
  • as utilizado para upcasting y tipo casting a tipo puente
  • as? utilizado para fundición segura, devuelve cero si falla
  • as! utilizado para forzar el lanzamiento, se bloquea si falla

Nota:

  • as! no se puede convertir el tipo sin formato a opcional

Ejemplos:

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

Ejemplo

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

Añadiendo un ?inmediatamente después del tipo de datos, le dice al compilador que la variable puede contener un número o no. ¡Ordenado! Tenga en cuenta que realmente no tiene sentido definir las constantes opcionales: puede establecer su valor solo una vez y, por lo tanto, podría decir si su valor será nulo o no.

¿Cuándo deberíamos usar "?" y cuando "!"

digamos que tenemos una aplicación simple basada en UIKit. tenemos algo de código en nuestro controlador de vista y queremos presentar un nuevo controlador de vista encima. y debemos decidir insertar la nueva vista en la pantalla usando el controlador de navegación.

Como sabemos, cada instancia de ViewController tiene un controlador de navegación de propiedad. Si está creando una aplicación basada en un controlador de navegación, esta propiedad del controlador de vista principal de su aplicación se configura automáticamente y puede usarla para empujar o abrir controladores de vista. Si usa una sola plantilla de proyecto de aplicación, no habrá un controlador de navegación creado automáticamente para usted, por lo que el controlador de vista predeterminado de su aplicación no tendrá nada almacenado en la propiedad navigationController.

Estoy seguro de que ya adivinó que este es exactamente un caso para un tipo de datos opcional. Si marca UIViewController, verá que la propiedad se define como:

var navigationController: UINavigationController? { get }

Así que volvamos a nuestro caso de uso. Si sabe con certeza que su controlador de vista siempre tendrá un controlador de navegación, puede continuar y forzar su desenvuelto:

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

Cuando pones un! detrás del nombre de la propiedad le dices al compilador que no me importa que esta propiedad sea opcional, sé que cuando este código se ejecuta siempre habrá un almacén de valor, así que trata este Opcional como un tipo de datos normal. Bueno, ¿no es agradable? Sin embargo, ¿qué pasaría si no hay un controlador de navegación en su controlador de vista? Si sugieres que siempre habrá un valor almacenado en navigationController, ¿estaba mal? Tu aplicación fallará. Tan simple y feo como eso.

¡Así que usa! solo si está 101% seguro de que es seguro.

¿Qué tal si no está seguro de que siempre habrá un controlador de navegación? ¿Entonces puedes usar? en vez de una !:

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

Que ? detrás del nombre de la propiedad le dice al compilador que no sé si esta propiedad contiene nil o un valor, así que: si tiene valor, úselo y, de lo contrario, solo considere la expresión completa nil.¿Efectivamente el? le permite usar esa propiedad solo en el caso de que haya un controlador de navegación. No si cheques de cualquier tipo o fundiciones de cualquier tipo. Esta sintaxis es perfecta cuando no le importa si tiene un controlador de navegación o no, y quiere hacer algo solo si lo hay.

Muchísimas gracias a Fantageek

SwiftBoy
fuente
8

Son dos formas diferentes de Downcasting en Swift.

( as?) , que se sabe que es la forma condicional , devuelve un valor opcional del tipo al que está intentando abatir.

Puede usarlo cuando no esté seguro de si el abatido tendrá éxito. Esta forma del operador siempre devolverá un valor opcional, y el valor será nulo si el abatimiento no fue posible. Esto le permite comprobar si hay un abatimiento exitoso.


( as!) , que se conoce como la forma forzada , intenta el abatido y desenvuelve el resultado como una sola acción compuesta.

Debe usarlo SÓLO cuando esté seguro de que el abatido siempre tendrá éxito. Esta forma del operador activará un error de tiempo de ejecución si intenta abatir a un tipo de clase incorrecto.

Para obtener más detalles, consulte la sección Type Casting de la documentación de Apple.

Scott Zhu
fuente
4

Quizás este ejemplo de código ayude a alguien a asimilar el 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
smileBot
fuente
Además, sea z2 = dict [2] as! String // "Yo" (no opcional)
Jay
-1

Soy novato en Swift y escribo este ejemplo tratando de explicar lo que entiendo sobre 'opcionales'. Si me equivoco, corrígeme.

Gracias.


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

Respuesta: (1) Aquí el programador está seguro de que “obj.lName”contiene un objeto de tipo cadena. Así que dale ese valor a “obj.lastName”.

Ahora, si el programador es correcto, significa que "obj.lName"es un objeto de tipo cadena, entonces no hay problema. "obj.lastName" se establecerá en el mismo valor.

Pero si el programador está equivocado significa "obj.lName"que no es un objeto de tipo cadena, es decir, contiene algún otro tipo de objeto como "NSNumber", etc. Entonces CRASH (Error de tiempo de ejecución).

(2) El programador no está seguro de que “obj.lName”contenga un objeto de tipo cadena o cualquier otro objeto de tipo. Así que establezca ese valor en “obj.lastName”si es de tipo cadena.

Ahora, si el programador es correcto, significa que “obj.lName”es un objeto de tipo cadena, entonces no hay problema.“obj.lastName”se establecerá en el mismo valor.

Pero si el programador está equivocado significa que obj.lName no es un objeto de tipo cadena, es decir, contiene algún otro tipo de objeto como "NSNumber"etc. Luego “obj.lastName”se establecerá en el valor nulo. Entonces, No Crash (Feliz :)

iPhoneBuddy
fuente