Opcionais de downcasting em Swift: como? Digite ou como! Tipo?

95

Dado o seguinte em Swift:

var optionalString: String?
let dict = NSDictionary()

Qual é a diferença prática entre as duas declarações a seguir:

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

vs

optionalString = dict.objectForKey("SomeKey") as! String?
sdduursma
fonte
1
Aslo Veja o como! Operadora da Apple
Honey

Respostas:

142

A diferença prática é esta:

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

optionalStringserá uma variável do tipo String?. Se o tipo subjacente for diferente de um, Stringisso será apenas atribuído nilao opcional sem causar danos .

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

Isso diz, eu sei que essa coisa é a String?. Isso também resultará em optionalStringser do tipo String?, mas travará se o tipo subjacente for outra coisa.

O primeiro estilo é então usado if letpara desembrulhar com segurança o 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
fonte
O primeiro método não é sempre melhor então? Ambos retornam um opcional do tipo String? Parece que o segundo método faz a mesma coisa que o primeiro, mas pode travar se o downcast não for bem-sucedido. Então, por que usá-lo?
Sikander
6
Sim @Sikander, o primeiro é sempre melhor. Eu nunca usaria o segundo.
vacawama de
14

as? Types- significa que o processo de fundição é opcional. O processo pode ser bem-sucedido ou não (o sistema retornará zero se o downcast falhar). Qualquer forma não irá travar se o downcast falhar.

as! Type?- Aqui o processo de down casting deve ser bem sucedido ( !indica que). O ponto de interrogação final indica se o resultado final pode ser nulo ou não.

Mais informações sobre "!" e "?"

Vamos pegar 2 casos

  1. Considerar:

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

    Aqui não sabemos se o resultado do downcast de célula com identificador "Cell" para UITableViewCell foi bem-sucedido ou não. Se malsucedido, ele retorna nulo (para evitar travamento aqui). Aqui podemos fazer conforme indicado abaixo.

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

    Portanto, vamos lembrar assim - se ?isso significa que não temos certeza se o valor é nulo ou não (o ponto de interrogação surge quando não sabemos as coisas).

  2. Compare isso com:

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

    Aqui dizemos ao compilador que a conversão deve ser bem-sucedida. Se falhar, o sistema irá travar. Portanto, damos !quando temos certeza de que o valor não é nulo.

Jishnu Bala
fonte
11

Para esclarecer o que vacawama disse, aqui está um exemplo ...

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ível
fonte
1 para o seu exemplo, mas você pode me explicar com o mesmo exemplo para usar como! no lugar de como? durante o downcast como let cell = tableView.dequeueReusableCellWithIdentifier ("Cell") as! UITableViewCell..eu acho que? era suficiente porque era a necessidade de como!
Anish Parajuli 웃
deixe cell = tableView.dequeueReusableCellWithIdentifier ("Cell") como? UITableViewCell. - aqui não sabemos se o resultado da conversão da célula com o identificador "Cell" para UITableViewCell é nulo ou não. Se nill, então ele retorna nill (para evitar travamento aqui).
jishnu bala
interessante, intNil as! String? // ==nilnão causa travamento !!! ???, como Opcional <Int> .Nenhum é diferente de Opcional <String> .Nenhum
onmyway133 01
por que estás abatida as?para String? Por que você não reduz para String?? Por que você não se abaixa as!para String?
Mel
Tentando fazer este playground no Swift 3, mas você tem que usar em Anyvez deAnyObject
Honey
9
  • as usado para upcasting e conversão de tipo para tipo com ponte
  • as? usado para fundição segura, retorna nulo se falhar
  • as! usado para forçar o lançamento, travar se falhar

Nota:

  • as! não pode converter o tipo bruto para opcional

Exemplos:

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

Exemplo

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

Adicionando um ?imediatamente após o tipo de dados, você informa ao compilador que a variável pode conter um número ou não. Arrumado! Observe que realmente não faz sentido definir constantes opcionais - você pode definir seu valor apenas uma vez e, portanto, seria capaz de dizer se seu valor será nulo ou não.

Quando devemos usar "?" e quando "!"

digamos que temos um aplicativo simples baseado em UIKit. temos algum código em nosso controlador de visualização e queremos apresentar um novo controlador de visualização em cima dele. e precisamos decidir empurrar a nova visualização na tela usando o controlador de navegação.

Como sabemos, cada instância de ViewController possui um controlador de navegação de propriedade. Se você estiver construindo um aplicativo baseado em controlador de navegação, esta propriedade do controlador de visualização mestre do seu aplicativo é definida automaticamente e você pode usá-lo para enviar ou abrir controladores de visualização. Se você usar um único modelo de projeto de aplicativo - não haverá um controlador de navegação criado automaticamente para você, então o controlador de visualização padrão do seu aplicativo não terá nada armazenado na propriedade navigationController.

Tenho certeza de que você já adivinhou que este é exatamente o caso de um tipo de dados opcional. Se você marcar UIViewController, verá que a propriedade está definida como:

var navigationController: UINavigationController? { get }

Portanto, vamos voltar ao nosso caso de uso. Se você sabe com certeza que seu controlador de visualização sempre terá um controlador de navegação, você pode ir em frente e forçar o desempacotamento:

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

Quando você coloca um! por trás do nome da propriedade, você diz ao compilador que não me importo se esta propriedade é opcional, eu sei que quando este código for executado sempre haverá um armazenamento de valor, portanto, trate este opcional como um tipo de dados normal. Bem, isso não é bom? O que aconteceria se não houvesse um controlador de navegação para o controlador de visualização? Se você sugeriu que sempre haverá um valor armazenado no navigationController estava errado? Seu aplicativo irá falhar. Simples e feio assim.

Então, use! somente se você tiver 101% de certeza de que isso é seguro.

Que tal se você não tem certeza de que sempre haverá um controlador de navegação? Então você pode usar? em vez de um !:

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

O que ? por trás do nome da propriedade diz ao compilador que não sei se esta propriedade contém nil ou um valor, então: se ela tiver valor, use-a e, caso contrário, considere a expressão inteira nil.Efetivamente, o? permite que você use essa propriedade apenas no caso de haver um controlador de navegação. Não se verifica de qualquer tipo ou fundições de qualquer tipo. Esta sintaxe é perfeita quando você não se importa se tem um controlador de navegação ou não, e deseja fazer algo apenas se houver.

Muito obrigado a Fantageek

SwiftBoy
fonte
8

São duas formas diferentes de Downcasting em Swift.

( as?) , que é conhecido como a Forma condicional , retorna um valor opcional do tipo para o qual você está tentando fazer o downcast.

Você pode usá-lo quando não tiver certeza se o downcast terá sucesso. Esta forma do operador sempre retornará um valor opcional, e o valor será nulo se o downcast não for possível. Isso permite que você verifique se o downcast foi bem-sucedido.


( as!) , que é conhecida como a Forma Forçada , tenta fazer o downcast e desembrulhar o resultado como uma única ação composta.

Você deve usá-lo SOMENTE quando tiver certeza de que o downcast sempre será bem-sucedido. Esta forma do operador irá disparar um erro de tempo de execução se você tentar fazer o downcast para um tipo de classe incorreto.

Para obter mais detalhes, consulte a seção Tipo de fundição da documentação da Apple.

Scott Zhu
fonte
4

Talvez este exemplo de código ajude alguém a entender o princípio:

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
fonte
Além disso, deixe z2 = dict [2] como! String // "Yo" (não opcional)
Jay
0

Pode ser mais fácil lembrar o padrão para esses operadores no Swift, pois: !implica "isso pode interceptar", enquanto? indica "isso pode ser nulo."

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

Zgpeace
fonte
-1

Eu sou novato em Swift e escrevo este exemplo tentando explicar como eu entendo sobre 'opcionais'. Se eu estiver errado, por favor me corrija.

Obrigado.


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

Resposta: (1) Aqui o programador tem certeza absoluta de que “obj.lName”contém o objeto do tipo string. Portanto, apenas dê esse valor para “obj.lastName”.

Agora, se o programador estiver correto, significa que "obj.lName"é um objeto do tipo string, então não há problema. "obj.lastName" será definido com o mesmo valor.

Mas se o programador estiver errado, significa que "obj.lName"não é um objeto do tipo string, ou seja, contém algum outro tipo de objeto como "NSNumber" etc. Então CRASH (Erro de tempo de execução).

(2) O programador não tem certeza se “obj.lName”contém o objeto do tipo string ou qualquer outro objeto de tipo. Portanto, defina esse valor para “obj.lastName”se for do tipo string.

Agora, se o programador estiver correto, significa que “obj.lName”é um objeto do tipo string, então não há problema. “obj.lastName”será definido com o mesmo valor.

Mas se o programador estiver errado, significa que obj.lName não é um objeto do tipo string, ou seja, contém algum outro objeto de tipo, como "NSNumber"etc. Então “obj.lastName”será definido com o valor nulo. Então, sem falha (feliz :)

iPhoneBuddy
fonte