Esta resposta é wiki da comunidade . Se você acha que poderia melhorar, fique à vontade para editá-lo !
Histórico: O que é opcional?
No Swift, Optional
é um tipo genérico que pode conter um valor (de qualquer tipo) ou nenhum valor.
Em muitas outras linguagens de programação, um valor "sentinela" específico é frequentemente usado para indicar uma falta de valor . No Objective-C, por exemplo, nil
(o ponteiro nulo ) indica a falta de um objeto. Mas isso fica mais complicado ao trabalhar com tipos primitivos - deve -1
ser usado para indicar a ausência de um número inteiro, ou talvez INT_MIN
, ou de algum outro número inteiro? Se qualquer valor específico for escolhido para significar "sem número inteiro", isso significa que ele não poderá mais ser tratado como um valor válido .
Swift é uma linguagem de segurança de tipo, o que significa que a linguagem ajuda você a ter clareza sobre os tipos de valores com os quais seu código pode trabalhar. Se parte do seu código espera uma String, o tipo safety impede que você transmita um Int por engano.
No Swift, qualquer tipo pode ser opcional . Um valor opcional pode assumir qualquer valor do tipo original ou do valor especial nil
.
Os opcionais são definidos com um ?
sufixo no tipo:
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
A falta de um valor em um opcional é indicada por nil
:
anOptionalInt = nil
(Observe que isso nil
não é o mesmo que nil
no Objetivo-C. No Objetivo-C, nil
é a ausência de um ponteiro de objeto válido ; no Swift, os Opcionais não estão restritos a objetos / tipos de referência. O opcional se comporta de maneira semelhante ao Talvez de Haskell .)
Por que recebi " erro fatal: inesperadamente encontrado nulo ao desembrulhar um valor opcional "?
Para acessar o valor de um opcional (se houver), você precisa desembrulhá- lo. Um valor opcional pode ser desembrulhado com segurança ou força. Se você forçar o desempacotamento de um opcional e ele não tiver um valor, seu programa falhará com a mensagem acima.
O Xcode mostrará a falha destacando uma linha de código. O problema ocorre nesta linha.
Essa falha pode ocorrer com dois tipos diferentes de desembrulhamento forçado:
1. Desembrulhamento forçado explícito
Isso é feito com o !
operador de forma opcional. Por exemplo:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
Erro fatal: inesperadamente encontrado nulo ao desembrulhar um valor opcional
Como anOptionalString
está nil
aqui, você terá um acidente na linha em que força a desembrulhar.
2. Opcionais implicitamente desembrulhados
Eles são definidos com a !
, e não ?
após o tipo.
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
Supõe-se que esses opcionais contenham um valor. Portanto, sempre que você acessar um opcional implicitamente desembrulhado, ele será automaticamente forçado a ser desembrulhado para você. Se não contiver um valor, ele falhará.
print(optionalDouble) // <- CRASH
Erro fatal: inesperadamente encontrado nulo ao desembrulhar implicitamente um valor opcional
Para descobrir qual variável causou a falha, mantenha pressionado ⌥enquanto clica para mostrar a definição, onde você pode encontrar o tipo opcional.
Os IBOutlets, em particular, geralmente são opcionais implicitamente desembrulhados. Isso ocorre porque o seu xib ou storyboard vinculará as saídas no tempo de execução, após a inicialização. Portanto, você deve garantir que não está acessando tomadas antes que elas sejam carregadas. Você também deve verificar se as conexões estão corretas no seu arquivo storyboard / xib, caso contrário, os valores estarão nil
em tempo de execução e, portanto, travarão quando forem desembrulhados implicitamente . Ao corrigir conexões, tente excluir as linhas de código que definem suas tomadas e reconecte-as.
Quando devo forçar a desembrulhar um opcional?
Desembrulhamento forçado explícito
Como regra geral, você nunca deve forçar explicitamente a desembrulhar um opcional com o !
operador. Pode haver casos em que o uso !
é aceitável - mas você só deve usá-lo se tiver 100% de certeza de que o opcional contém um valor.
Enquanto não pode ser uma ocasião em que você pode usar a força desembrulhar, como você sabe para um fato que um opcional contém um valor - não há um único lugar onde você não pode com segurança unwrap que opcional vez.
Opcionais implicitamente desembrulhados
Essas variáveis foram projetadas para que você possa adiar sua atribuição até mais tarde no seu código. É sua responsabilidade garantir que eles tenham um valor antes de você acessá-los. No entanto, como envolvem o desembrulhamento forçado, eles ainda são inerentemente inseguros - pois assumem que seu valor é nulo, mesmo que a atribuição nula seja válida.
Você deve usar apenas opcionais implicitamente desembrulhados como último recurso . Se você pode usar uma variável lenta ou fornecer um valor padrão para uma variável - faça-o em vez de usar um opcional implicitamente desembrulhado.
No entanto, existem alguns cenários em que os opcionais implicitamente desembrulhados são benéficos , e você ainda pode usar várias maneiras de desembrulhá-los com segurança, conforme listado abaixo - mas você deve sempre usá-los com a devida cautela.
Como posso lidar com segurança com os opcionais?
A maneira mais simples de verificar se um opcional contém um valor é compará-lo com nil
.
if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
No entanto, 99,9% do tempo ao trabalhar com opcionais, na verdade, você deseja acessar o valor que ele contém, se houver algum. Para fazer isso, você pode usar a opção de ligação .
Encadernação opcional
Ligação opcional permite verificar se um opcional contém um valor - e permite atribuir o valor desembrulhado a uma nova variável ou constante. Ele usa a sintaxe if let x = anOptional {...}
ou if var x = anOptional {...}
, dependendo se você precisar modificar o valor da nova variável após vinculá-la.
Por exemplo:
if let number = anOptionalInt {
print("Contains a value! It is \(number)!")
} else {
print("Doesn’t contain a number")
}
O que isso faz é primeiro verificar se o opcional contém um valor. Se isso acontecer , o valor 'desembrulhado' é atribuído a uma nova variável ( number
) - que você pode usar livremente como se não fosse opcional. Se o opcional não contiver um valor, a cláusula else será invocada, como seria de esperar.
O interessante da ligação opcional é que você pode desembrulhar vários opcionais ao mesmo tempo. Você pode apenas separar as instruções com uma vírgula. A declaração terá êxito se todos os opcionais forem desembrulhados.
var anOptionalInt : Int?
var anOptionalString : String?
if let number = anOptionalInt, let text = anOptionalString {
print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
print("One or more of the optionals don’t contain a value")
}
Outro truque interessante é que você também pode usar vírgulas para verificar uma determinada condição no valor, depois de desembrulhá-lo.
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
O único problema com o uso de ligação opcional em uma instrução if é que você só pode acessar o valor desembrulhado no escopo da instrução. Se você precisar acessar o valor de fora do escopo da declaração, poderá usar uma declaração de guarda .
Uma declaração de guarda permite definir uma condição para o sucesso - e o escopo atual só continuará sendo executado se essa condição for atendida. Eles são definidos com a sintaxe guard condition else {...}
.
Portanto, para usá-los com uma ligação opcional, você pode fazer o seguinte:
guard let number = anOptionalInt else {
return
}
(Observe que dentro do corpo de guarda, você deve usar uma das instruções de transferência de controle para sair do escopo do código atualmente em execução).
Se anOptionalInt
contiver um valor, ele será desempacotado e atribuído à nova number
constante. O código após o guarda continuará executando. Se não contiver um valor - o guarda executará o código entre parênteses, o que levará à transferência do controle, para que o código imediatamente a seguir não seja executado.
O mais interessante sobre as instruções de guarda é que o valor desembrulhado está agora disponível para uso no código que segue a instrução (como sabemos que o código futuro só poderá ser executado se o opcional tiver um valor). Isso é ótimo para eliminar 'pirâmides da desgraça' criadas ao aninhar várias instruções if.
Por exemplo:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
Os guardas também suportam os mesmos truques que a instrução if suportava, como desembrulhar vários opcionais ao mesmo tempo e usar a where
cláusula.
Se você usa uma instrução if ou guard depende completamente se algum código futuro exige que o opcional contenha um valor.
Operador de coalescência zero
O Operador de coalizão nulo é uma versão simplificada do operador condicional ternário , projetado principalmente para converter opcionais em não opcionais. Possui a sintaxe a ?? b
, onde a
é um tipo opcional e b
é do mesmo tipo a
(embora geralmente não seja opcional).
Essencialmente, permite que você diga “Se a
contém um valor, desembrulhe-o. Se não, então retorne b
”. Por exemplo, você pode usá-lo assim:
let number = anOptionalInt ?? 0
Isso definirá uma number
constante do Int
tipo, que conterá o valor de anOptionalInt
, se ele contiver um valor ou 0
outro.
É apenas uma abreviação para:
let number = anOptionalInt != nil ? anOptionalInt! : 0
Encadeamento opcional
Você pode usar o Encadeamento opcional para chamar um método ou acessar uma propriedade em um opcional. Isso é feito simplesmente com o sufixo do nome da variável com a ?
ao usá-lo.
Por exemplo, digamos que temos uma variável foo
, do tipo uma Foo
instância opcional .
var foo : Foo?
Se quisermos chamar um método foo
que não retorne nada, podemos simplesmente fazer:
foo?.doSomethingInteresting()
Se foo
contiver um valor, este método será chamado nele. Caso contrário, nada de ruim acontecerá - o código simplesmente continuará executando.
(Esse é um comportamento semelhante ao envio de mensagens nil
no Objective-C)
Portanto, isso também pode ser usado para definir propriedades e chamar métodos. Por exemplo:
foo?.bar = Bar()
Novamente, nada de ruim vai acontecer aqui, se foo
for nil
. Seu código simplesmente continuará executando.
Outro truque interessante que o encadeamento opcional permite é verificar se a configuração de uma propriedade ou a chamada de um método foi bem-sucedida. Você pode fazer isso comparando o valor de retorno com nil
.
(Isso ocorre porque um valor opcional retornará, Void?
e não Void
em um método que não retorna nada)
Por exemplo:
if (foo?.bar = Bar()) != nil {
print("bar was set successfully")
} else {
print("bar wasn’t set successfully")
}
No entanto, as coisas se tornam um pouco mais complicadas ao tentar acessar propriedades ou chamar métodos que retornam um valor. Por foo
ser opcional, tudo o que for retornado também será opcional. Para lidar com isso, você pode desembrulhar os opcionais que são retornados usando um dos métodos acima - ou desembrulhar- foo
se antes de acessar métodos ou chamar métodos que retornam valores.
Além disso, como o nome sugere, você pode 'encadear' essas instruções juntas. Isso significa que se foo
possui uma propriedade opcional baz
, que possui uma propriedade qux
- você pode escrever o seguinte:
let optionalQux = foo?.baz?.qux
Novamente, porque foo
e baz
são opcionais, o valor retornado qux
será sempre opcional, independentemente de qux
ele próprio ser opcional.
map
e flatMap
Um recurso frequentemente subutilizado com opcionais é a capacidade de usar as funções map
e flatMap
. Isso permite aplicar transformações não opcionais a variáveis opcionais. Se um opcional tiver um valor, você poderá aplicar uma determinada transformação a ele. Se não tiver um valor, permanecerá nil
.
Por exemplo, digamos que você tenha uma sequência opcional:
let anOptionalString:String?
Aplicando a map
função a ela - podemos usar a stringByAppendingString
função para concatená-la em outra string.
Como stringByAppendingString
usa um argumento de cadeia não opcional, não podemos inserir nossa cadeia opcional diretamente. No entanto, usando map
, podemos usar allow stringByAppendingString
para ser usado se anOptionalString
tiver um valor.
Por exemplo:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
No entanto, se anOptionalString
não tiver um valor, map
retornará nil
. Por exemplo:
var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
flatMap
funciona de maneira semelhante a map
, exceto que permite retornar outro opcional de dentro do corpo do fechamento. Isso significa que você pode inserir uma opção opcional em um processo que requer uma entrada não opcional, mas pode gerar uma opção opcional.
try!
O sistema de tratamento de erros da Swift pode ser usado com segurança com o Do-Try-Catch :
do {
let result = try someThrowingFunc()
} catch {
print(error)
}
Se someThrowingFunc()
lançar um erro, o erro será capturado com segurança no catch
bloco.
A error
constante que você vê no catch
bloco não foi declarada por nós - é gerada automaticamente por catch
.
Você também pode error
se declarar , tem a vantagem de poder convertê-lo em um formato útil, por exemplo:
do {
let result = try someThrowingFunc()
} catch let error as NSError {
print(error.debugDescription)
}
Usar try
esta maneira é a maneira correta de tentar, capturar e manipular erros provenientes de funções de lançamento.
Há também o try?
que absorve o erro:
if let result = try? someThrowingFunc() {
// cool
} else {
// handle the failure, but there's no error information available
}
Mas o sistema de tratamento de erros da Swift também fornece uma maneira de "forçar a tentativa" com try!
:
let result = try! someThrowingFunc()
Os conceitos explicados neste post também se aplicam aqui: se um erro for lançado, o aplicativo falhará.
Você só deve usar try!
se puder provar que seu resultado nunca falhará no seu contexto - e isso é muito raro.
Na maioria das vezes, você usará o sistema Do-Try-Catch completo - e o opcional try?
, nos raros casos em que o tratamento do erro não é importante.
Recursos