O que é um "valor desembrulhado" no Swift?

155

Estou aprendendo o Swift para iOS 8 / OSX 10.10 seguindo este tutorial , e o termo " valor desembrulhado " é usado várias vezes, como neste parágrafo (em Objetos e classe ):

Ao trabalhar com valores opcionais, você pode escrever? antes de operações como métodos, propriedades e assinatura. Se o valor antes do? é nulo, tudo depois do? é ignorado e o valor de toda a expressão é nulo. Caso contrário, o valor opcional é desembrulhado e tudo após o? atua sobre o valor desembrulhado . Nos dois casos, o valor da expressão inteira é um valor opcional.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare?.sideLength

Não entendi e procurei na web sem sorte.

O que isso significa?


Editar

Da resposta de Cezary, há uma pequena diferença entre a saída do código original e a solução final (testada no playground):

Código original

Código original

Solução de Cezary

Solução de Cezary

As propriedades da superclasse são mostradas na saída no segundo caso, enquanto há um objeto vazio no primeiro caso.

O resultado não deveria ser idêntico nos dois casos?

Perguntas e respostas relacionadas: O que é um valor opcional no Swift?

Bigood
fonte
1
A resposta de Cezary é imediata. Além disso, há um ótimo livro de introdução ao Swift disponível gratuitamente no iBooks
Daniel Storm
@DanielStormApps Este iBook é uma versão portátil do tutorial ligada na minha pergunta :)
Bigood

Respostas:

289

Primeiro, você precisa entender o que é um tipo opcional. Um tipo opcional basicamente significa que a variável pode ser nil .

Exemplo:

var canBeNil : Int? = 4
canBeNil = nil

O ponto de interrogação indica o fato que canBeNilpode ser nil.

Isso não funcionaria:

var cantBeNil : Int = 4
cantBeNil = nil // can't do this

Para obter o valor da sua variável, se for opcional, você deve desembrulhá-lo . Isso significa apenas colocar um ponto de exclamação no final.

var canBeNil : Int? = 4
println(canBeNil!)

Seu código deve ficar assim:

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare!.sideLength

Uma nota lateral:

Você também pode declarar opcionais para desembrulhar automaticamente usando um ponto de exclamação em vez de um ponto de interrogação.

Exemplo:

var canBeNil : Int! = 4
print(canBeNil) // no unwrapping needed

Portanto, uma maneira alternativa de corrigir seu código é:

let optionalSquare: Square! = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare.sideLength

EDITAR:

A diferença que você está vendo é exatamente o sintoma do fato de o valor opcional estar quebrado . Há outra camada em cima dela. A versão desembrulhada apenas mostra o objeto reto porque é, bem, desembrulhado.

Uma rápida comparação do playground:

Parque infantil

No primeiro e no segundo casos, o objeto não está sendo desembrulhado automaticamente, então você vê duas "camadas" ( {{...}}), enquanto no terceiro caso, você vê apenas uma camada ( {...}) porque o objeto está sendo desembrulhado automaticamente.

A diferença entre o primeiro caso e os segundos dois casos é que os dois segundos casos fornecerão um erro de tempo de execução se optionalSquareestiver definido como nil. Usando a sintaxe no primeiro caso, você pode fazer algo assim:

if let sideLength = optionalSquare?.sideLength {
    println("sideLength is not nil")
} else {
    println("sidelength is nil")
}
Cezary Wojcik
fonte
1
Obrigado pela explicação clara! O código incluído na minha pergunta é citado no tutorial da Apple (link na pergunta) e acho que deve funcionar como está (e funciona). Porém, sua solução final e a original geram resultados diferentes (veja minha edição). Alguma idéia?
Bigood
2
Legal. Ótima explicação. Ainda estou confuso, no entanto. Por que eu gostaria de usar um valor opcional? Quando é útil? Por que a Apple criou esse comportamento e sintaxe?
Todd Lehman
1
É particularmente relevante quando se trata de propriedades de classe. O Swift exige que todos os métodos não opcionais sejam inicializados na init()função da classe. Eu recomendo a leitura dos capítulos "The Basics" (abrange opcionais), "Inicialização" e "Encadeamento opcional" no livro Swift lançado pela Apple.
Cezary Wojcik
2
@ToddLehman A Apple criou isso para ajudá-lo a escrever um código muito mais seguro. Por exemplo, imagine todas as exceções de ponteiro nulo em Java travando um programa porque alguém esqueceu de verificar se há nulo. Ou comportamento estranho no Objective-C, porque você esqueceu de verificar se não há nada. Com o tipo opcional, você não pode esquecer de lidar com nada. O compilador obriga a lidar com isso.
Erik Engheim
5
@AdamSmith - Vindo de um plano de fundo Java, posso ver definitivamente o uso dos opcionais ... mas também (mais recentemente) de um plano de Objective-C, ainda estou tendo problemas para ver o uso dele, porque Objective- C permite explicitamente que você envie mensagens para objetos nulos. Hmm. Eu adoraria ver alguém escrever um exemplo disso mostrando como eles ajudam a tornar o código mais curto e seguro do que o código equivalente que não os usa. Não estou dizendo que eles não são úteis; Só estou dizendo que ainda não entendi.
Todd Lehman
17

A resposta correta existente é ótima, mas eu achei que para entender isso completamente, eu precisava de uma boa analogia, pois esse é um conceito muito abstrato e estranho.

Então, deixe-me ajudar os colegas desenvolvedores "de cérebro direito" (pensamento visual) dando uma perspectiva diferente, além da resposta correta. Aqui está uma boa analogia que me ajudou muito.

Presente de aniversário

Pense nos opcionais como sendo presentes de aniversário que vêm em embalagens rígidas, duras e coloridas.

Você não sabe se há algo dentro do embrulho até desembrulhar o presente - talvez não haja nada lá dentro! Se houver algo dentro, pode ser outro presente, que também está embrulhado e que também pode não conter nada . Você pode até desembrulhar 100 presentes aninhados para finalmente descobrir que não havia nada além de embalagem .

Se o valor do opcional não for nil, agora você revelou uma caixa contendo algo . Mas, especialmente se o valor não for digitado explicitamente e for uma variável e não uma constante predefinida, você ainda precisará abrir a caixa para poder saber algo específico sobre o que está na caixa, como que tipo ou qual é o valor valor real é.

O que está na caixa?! Analogia

Mesmo depois de desembrulhar a variável, você ainda é como Brad Pitt na última cena de SE7EN ( aviso : spoilers e linguagem suja e violência muito cotada para R), porque mesmo depois de desembrulhar o presente, você está na seguinte situação: agora você tem nilou uma caixa contendo algo (mas você não sabe o que).

Você pode saber o tipo de algo . Por exemplo, se você declarasse a variável como sendo do tipo, [Int:Any?]saberia que tinha um Dicionário (potencialmente vazio) com subscritos inteiros que produzem conteúdos agrupados de qualquer tipo antigo.

É por isso que lidar com tipos de coleção (dicionários e matrizes) no Swift pode ficar meio peludo.

Caso em questão:

typealias presentType = [Int:Any?]

func wrap(i:Int, gift:Any?) -> presentType? {
    if(i != 0) {
        let box : presentType = [i:wrap(i-1,gift:gift)]
        return box
    }
    else {
        let box = [i:gift]
        return box
    }
}

func getGift() -> String? {
    return "foobar"
}

let f00 = wrap(10,gift:getGift())

//Now we have to unwrap f00, unwrap its entry, then force cast it into the type we hope it is, and then repeat this in nested fashion until we get to the final value.

var b4r = (((((((((((f00![10]! as! [Int:Any?])[9]! as! [Int:Any?])[8]! as! [Int:Any?])[7]! as! [Int:Any?])[6]! as! [Int:Any?])[5]! as! [Int:Any?])[4]! as! [Int:Any?])[3]! as! [Int:Any?])[2]! as! [Int:Any?])[1]! as! [Int:Any?])[0])

//Now we have to DOUBLE UNWRAP the final value (because, remember, getGift returns an optional) AND force cast it to the type we hope it is

let asdf : String = b4r!! as! String

print(asdf)
CommaToast
fonte
O que você achou?
SamSol 4/16
ame a analogia! então, existe uma maneira melhor de desembrulhar as caixas? no seu exemplo de código
David T.
O gato de Schrödinger está na caixa
Jacksonkr
Obrigado por me confundir ... Que tipo de programador dá um presente de aniversário vazio de qualquer maneira? meio que significa
delta2flat
@Jacksonkr Ou não.
amsmath
13

A Swift atribui um alto valor à segurança do tipo. Toda a linguagem Swift foi projetada com segurança em mente. É uma das marcas registradas da Swift e que você deve receber de braços abertos. Ele ajudará no desenvolvimento de código limpo e legível e ajudará a impedir que seu aplicativo falhe.

Todos os opcionais no Swift são demarcados com o ?símbolo. Ao definir o ?depois do nome do tipo em que você está declarando como opcional, você basicamente está convertendo isso não como o tipo anterior a ?, mas como o tipo opcional .


Nota: Uma variável ou tipo nãoInt é o mesmo que . São dois tipos diferentes que não podem ser operados um com o outro.Int?


Usando opcional

var myString: String?

myString = "foobar"

Isso não significa que você está trabalhando com um tipo de String. Isso significa que você está trabalhando com um tipo de String?(String Optional ou Optional String). De fato, sempre que você tenta

print(myString)

em tempo de execução, o console de depuração será impresso Optional("foobar"). A Optional()parte " " indica que esta variável pode ou não ter um valor em tempo de execução, mas acontece que atualmente está contendo a string "foobar". Esta Optional()indicação " " permanecerá, a menos que você faça o que é chamado "desembrulhando" o valor opcional.

Desembrulhar um opcional significa que agora você está convertendo esse tipo como não opcional. Isso irá gerar um novo tipo e atribuir o valor que residia dentro desse opcional ao novo tipo não opcional. Dessa forma, você pode executar operações nessa variável, pois foi garantido pelo compilador um valor sólido.


O desempacotamento condicional verificará se o valor no opcional é nilou não. Caso contrário nil, haverá uma variável constante recém-criada, à qual será atribuído o valor e desembrulhado na constante não opcional. E a partir daí você pode usar com segurança o não opcional no ifbloco.

Nota: Você pode dar à sua constante desembrulhada condicionalmente o mesmo nome da variável opcional que está sendo desembrulhada.

if let myString = myString {
    print(myString)
    // will print "foobar"
}

Desdobrar condicionalmente os opcionais é a maneira mais limpa de acessar o valor de um opcional, porque se ele contiver um valor nulo, tudo dentro do bloco if let não será executado. Obviamente, como qualquer instrução if, você pode incluir um bloco else

if let myString = myString {
    print(myString)
    // will print "foobar"
}
else {
    print("No value")
}

O desembrulhamento forçado é feito empregando o que é conhecido como !operador ("bang"). Isso é menos seguro, mas ainda permite que seu código seja compilado. No entanto, sempre que você usa o operador bang, você deve ter 1000% de certeza de que sua variável realmente contém um valor sólido antes de desembrulhar à força.

var myString: String?

myString = "foobar"

print(myString!)

Isso acima é um código Swift totalmente válido. Ele imprime o valor myStringdefinido como "foobar". O usuário veria foobarimpresso no console e é isso. Mas vamos assumir que o valor nunca foi definido:

var myString: String?

print(myString!) 

Agora temos uma situação diferente em nossas mãos. Diferentemente do Objective-C, sempre que é feita uma tentativa de desembrulhar à força um opcional, o opcional não foi definido e é nil, quando você tenta desembrulhar o opcional para ver o que está dentro do seu aplicativo falhará.


Desembrulhando w / Type Casting . Como dissemos anteriormente, enquanto você é unwrappingo opcional, na verdade, está convertendo para um tipo não opcional, também pode converter o não opcional para um tipo diferente. Por exemplo:

var something: Any?

Em algum lugar do nosso código, a variável somethingserá definida com algum valor. Talvez estejamos usando genéricos ou talvez exista alguma outra lógica que faça com que isso mude. Portanto, mais tarde em nosso código, queremos usar, somethingmas ainda podemos tratá-lo de maneira diferente se for um tipo diferente. Nesse caso, você desejará usar a aspalavra-chave para determinar isso:

Nota: O asoperador é como você digita a conversão em Swift.

// Conditionally
if let thing = something as? Int {
    print(thing) // 0
}

// Optionally
let thing = something as? Int
print(thing) // Optional(0)

// Forcibly
let thing = something as! Int
print(thing) // 0, if no exception is raised

Observe a diferença entre as duas aspalavras-chave. Como antes, quando desembrulhamos à força um opcional, usamos o !operador bang para fazer isso. Aqui você fará a mesma coisa, mas, em vez de lançar como apenas um não opcional, você também estará lançando como Int. E deve poder ser abatido Int, caso contrário, como usar o operador bang quando o valor for nilseu aplicativo falhará.

E para usar essas variáveis ​​em algum tipo ou operação matemática, elas devem ser desembrulhadas para fazer isso.

Por exemplo, no Swift, apenas tipos de dados de número válido do mesmo tipo podem ser operados um contra o outro. Quando você lança um tipo com as!você, está forçando o downcast dessa variável como se tivesse certeza de que é desse tipo, portanto, é seguro operar e não travar seu aplicativo. Tudo bem, desde que a variável seja do tipo para a qual você está lançando, caso contrário, você terá uma bagunça nas mãos.

No entanto, a conversão com as!permitirá que seu código seja compilado. Fundir com um as?é uma história diferente. De fato, as?declara o seu Intcomo um tipo de dados completamente diferente.

Agora é Optional(0)

E se você já tentou fazer sua lição de casa escrevendo algo como

1 + Optional(1) = 2

Seu professor de matemática provavelmente teria lhe dado um "F". O mesmo com Swift. Exceto que Swift prefere não compilar, em vez de dar uma nota. Porque no final do dia, o opcional pode de fato ser nulo .

Segurança First Kids.

Brandon A
fonte
Boa explicação dos tipos opcionais. Ajudou a esclarecer as coisas para mim.
Tobe_Sta
0

'?' significa expressão opcional de encadeamento,

o '!' significa valor de força
insira a descrição da imagem aqui

gkgy
fonte