Um opcional no Swift é um tipo que pode conter um valor ou nenhum valor. Os opcionais são gravados anexando a ?
a qualquer tipo:
var name: String? = "Bertie"
Os opcionais (juntamente com os genéricos) são um dos conceitos Swift mais difíceis de entender. Por causa de como eles são escritos e usados, é fácil ter uma idéia errada do que são. Compare o opcional acima para criar uma String normal:
var name: String = "Bertie" // No "?" after String
A partir da sintaxe, parece que uma String opcional é muito semelhante a uma String comum. Não é. Uma String opcional não é uma String com alguma configuração "opcional" ativada. Não é uma variedade especial de String. Uma String e uma String opcional são tipos completamente diferentes.
Aqui está a coisa mais importante a saber: Um opcional é um tipo de contêiner. Uma String opcional é um contêiner que pode conter uma String. Um Int opcional é um contêiner que pode conter um Int. Pense em um opcional como um tipo de parcela. Antes de abri-lo (ou "desembrulhar" no idioma dos opcionais), você não saberá se ele contém algo ou nada.
Você pode ver como os opcionais são implementados na Swift Standard Library digitando "Opcional" em qualquer arquivo Swift e clicando ⌘ nele. Aqui está a parte importante da definição:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Opcional é apenas um enum
que pode ser um dos dois casos: .none
ou .some
. Se for .some
, existe um valor associado que, no exemplo acima, seria o String
"Olá". Um opcional usa Genéricos para atribuir um tipo ao valor associado. O tipo de uma String opcional não é String
, é Optional
ou mais precisamente Optional<String>
.
Tudo o que o Swift faz com os opcionais é mágico para tornar a leitura e a escrita de códigos mais fluentes. Infelizmente, isso obscurece a maneira como realmente funciona. Vou passar por alguns truques mais tarde.
Nota: falarei muito sobre variáveis opcionais, mas também é bom criar constantes opcionais. Marquei todas as variáveis com seus tipos para facilitar a compreensão dos tipos de tipos que estão sendo criados, mas você não precisa fazer isso em seu próprio código.
Como criar opcionais
Para criar um opcional, anexe a ?
após o tipo que você deseja agrupar. Qualquer tipo pode ser opcional, até mesmo seus próprios tipos personalizados. Você não pode ter um espaço entre o tipo e o ?
.
var name: String? = "Bob" // Create an optional String that contains "Bob"
var peter: Person? = Person() // An optional "Person" (custom type)
// A class with a String and an optional String property
class Car {
var modelName: String // must exist
var internalName: String? // may or may not exist
}
Usando opcionais
Você pode comparar um opcional nil
para ver se ele possui um valor:
var name: String? = "Bob"
name = nil // Set name to nil, the absence of a value
if name != nil {
print("There is a name")
}
if name == nil { // Could also use an "else"
print("Name has no value")
}
Isto é um pouco confuso. Isso implica que um opcional é uma coisa ou outra. É nulo ou é "Bob". Isso não é verdade, o opcional não se transforma em outra coisa. Compará-lo a zero é um truque para tornar o código mais fácil de ler. Se um opcional for igual a zero, isso significa apenas que o enum está atualmente definido como .none
.
Somente opcionais podem ser nulos
Se você tentar definir uma variável não opcional como nula, receberá um erro.
var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'
Outra maneira de ver os opcionais é como um complemento às variáveis Swift normais. Eles são uma contrapartida de uma variável que é garantida como tendo um valor. Swift é uma linguagem cuidadosa que odeia ambiguidade. A maioria das variáveis é definida como não opcional, mas às vezes isso não é possível. Por exemplo, imagine um controlador de exibição que carrega uma imagem de um cache ou da rede. Pode ou não ter essa imagem no momento em que o controlador de exibição é criado. Não há como garantir o valor da variável de imagem. Nesse caso, você precisaria torná-lo opcional. Inicia como nil
e quando a imagem é recuperada, o opcional recebe um valor.
O uso de um opcional revela a intenção dos programadores. Comparado ao Objective-C, onde qualquer objeto pode ser nulo, o Swift precisa que você seja claro sobre quando um valor pode estar faltando e quando é garantido que ele existe.
Para usar um opcional, você o "desembrulha"
Um opcional String
não pode ser usado no lugar de um real String
. Para usar o valor agrupado dentro de um opcional, você deve desembrulhá-lo. A maneira mais simples de desembrulhar um opcional é adicionar um !
após o nome opcional. Isso é chamado de "força de desembrulhar". Ele retorna o valor dentro do opcional (como o tipo original), mas se o opcional for nil
, causa uma falha no tempo de execução. Antes de desembrulhar, verifique se há um valor.
var name: String? = "Bob"
let unwrappedName: String = name!
print("Unwrapped name: \(unwrappedName)")
name = nil
let nilName: String = name! // Runtime crash. Unexpected nil.
Verificando e usando um opcional
Como você sempre deve verificar o valor nulo antes de desembrulhar e usar um opcional, este é um padrão comum:
var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
let unwrappedMealPreference: String = mealPreference!
print("Meal: \(unwrappedMealPreference)") // or do something useful
}
Nesse padrão, você verifica se um valor está presente e, quando tiver certeza, é forçado a desembrulhá-lo em uma constante temporária para uso. Como isso é algo comum, o Swift oferece um atalho usando "if let". Isso é chamado de "ligação opcional".
var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
print("Meal: \(unwrappedMealPreference)")
}
Isso cria uma constante temporária (ou variável, se você substituir let
por var
), cujo escopo está apenas dentro das chaves do if. Como ter que usar um nome como "desempacotadoMealPreference" ou "realMealPreference" é um fardo, o Swift permite reutilizar o nome da variável original, criando um nome temporário no escopo do colchete
var mealPreference: String? = "Vegetarian"
if let mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // separate from the other mealPreference
}
Aqui está um código para demonstrar que uma variável diferente é usada:
var mealPreference: String? = "Vegetarian"
if var mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // mealPreference is a String, not a String?
mealPreference = "Beef" // No effect on original
}
// This is the original mealPreference
print("Meal: \(mealPreference)") // Prints "Meal: Optional("Vegetarian")"
A ligação opcional funciona verificando se o opcional é igual a zero. Caso contrário, desembrulha o opcional na constante fornecida e executa o bloco. No Xcode 8.3 e posterior (Swift 3.1), tentar imprimir um opcional como esse causará um aviso inútil. Use os opcionais debugDescription
para silenciá-lo:
print("\(mealPreference.debugDescription)")
Para que são opcionais?
Os opcionais têm dois casos de uso:
- Coisas que podem falhar (eu esperava algo, mas não consegui nada)
- Coisas que não são nada agora, mas que podem ser algo mais tarde (e vice-versa)
Alguns exemplos concretos:
- Uma propriedade que pode estar lá ou não, como
middleName
ou spouse
em uma Person
classe
- Um método que pode retornar um valor ou nada, como procurar uma correspondência em uma matriz
- Um método que pode retornar um resultado ou obter um erro e não retornar nada, como tentar ler o conteúdo de um arquivo (que normalmente retorna os dados do arquivo), mas o arquivo não existe
- Delegar propriedades, que nem sempre precisam ser definidas e geralmente são definidas após a inicialização
- Para
weak
propriedades em classes. O que eles apontam pode ser definido a nil
qualquer momento
- Um grande recurso que talvez precise ser liberado para recuperar a memória
- Quando você precisar saber como um valor foi definido (dados ainda não carregados> os dados) em vez de usar um dataLoaded separado
Boolean
Os opcionais não existem no Objective-C, mas há um conceito equivalente, retornando nulo. Métodos que podem retornar um objeto podem retornar nulo. Isso significa "a ausência de um objeto válido" e é frequentemente usado para dizer que algo deu errado. Ele funciona apenas com objetos Objective-C, não com primitivos ou tipos C básicos (enumerações, estruturas). O Objective-C geralmente tinha tipos especializados para representar a ausência desses valores (o NSNotFound
que é realmente NSIntegerMax
, kCLLocationCoordinate2DInvalid
para representar uma coordenada inválida -1
ou também é usado algum valor negativo). O codificador precisa conhecer esses valores especiais para que eles sejam documentados e aprendidos para cada caso. Se um método não pode tomar nil
como parâmetro, isso deve ser documentado. No Objetivo-C,nil
era um ponteiro, assim como todos os objetos eram definidos como ponteiros, mas nil
apontavam para um endereço (zero) específico. Em Swift, nil
é um literal que significa a ausência de um determinado tipo.
Comparando à nil
Você costumava usar qualquer opcional como Boolean
:
let leatherTrim: CarExtras? = nil
if leatherTrim {
price = price + 1000
}
Nas versões mais recentes do Swift, você precisa usar leatherTrim != nil
. Por que é isso? O problema é que um Boolean
pode ser envolvido em um opcional. Se você tem Boolean
assim:
var ambiguous: Boolean? = false
ele tem dois tipos de "false", um onde não há valor e outro onde ele tem um valor, mas o valor é false
. Swift odeia ambiguidade, então agora você deve sempre verificar um opcional contra nil
.
Você pode se perguntar qual é o objetivo de um opcional Boolean
? Como com outros opcionais, o .none
estado pode indicar que o valor ainda é desconhecido. Pode haver algo do outro lado de uma chamada de rede que leva algum tempo para ser pesquisado. Booleanos opcionais também são chamados de " Booleanos de Três Valores "
Truques rápidos
O Swift usa alguns truques para permitir que os opcionais funcionem. Considere estas três linhas de código opcional com aparência comum;
var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }
Nenhuma dessas linhas deve compilar.
- A primeira linha define uma String opcional usando um literal String, dois tipos diferentes. Mesmo se este fosse um,
String
os tipos são diferentes
- A segunda linha define uma String opcional como nula, dois tipos diferentes
- A terceira linha compara uma string opcional a zero, dois tipos diferentes
Vou examinar alguns dos detalhes de implementação de opcionais que permitem que essas linhas funcionem.
Criando um opcional
Usar ?
para criar um opcional é o açúcar sintático, ativado pelo compilador Swift. Se você quiser fazer isso da maneira mais longa, poderá criar um opcional como este:
var name: Optional<String> = Optional("Bob")
Isso chama Optional
o primeiro inicializador, public init(_ some: Wrapped)
que infere o tipo associado do opcional do tipo usado entre parênteses.
A maneira ainda mais longa de criar e definir um opcional:
var serialNumber:String? = Optional.none
serialNumber = Optional.some("1234")
print("\(serialNumber.debugDescription)")
Definir um opcional para nil
Você pode criar um opcional sem valor inicial ou criar um com o valor inicial de nil
(ambos têm o mesmo resultado).
var name: String?
var name: String? = nil
Permitir que os opcionais sejam iguais nil
é ativado pelo protocolo ExpressibleByNilLiteral
(nomeado anteriormente NilLiteralConvertible
). O opcional é criado com Optional
o segundo inicializador public init(nilLiteral: ())
,. Os documentos dizem que você não deve usar ExpressibleByNilLiteral
nada além de opcionais, pois isso mudaria o significado de zero no seu código, mas é possível fazer isso:
class Clint: ExpressibleByNilLiteral {
var name: String?
required init(nilLiteral: ()) {
name = "The Man with No Name"
}
}
let clint: Clint = nil // Would normally give an error
print("\(clint.name)")
O mesmo protocolo permite definir um opcional já criado como nil
. Embora não seja recomendado, você pode usar o inicializador literal nulo diretamente:
var name: Optional<String> = Optional(nilLiteral: ())
Comparando um opcional para nil
Os opcionais definem dois operadores "==" e "! =" Especiais, que você pode ver na Optional
definição. O primeiro ==
permite que você verifique se algum opcional é igual a zero. Dois opcionais diferentes definidos como .none sempre serão iguais se os tipos associados forem os mesmos. Quando você compara com zero, nos bastidores, o Swift cria um opcional do mesmo tipo associado, definido como .none e o utiliza para a comparação.
// How Swift actually compares to nil
var tuxedoRequired: String? = nil
let temp: Optional<String> = Optional.none
if tuxedoRequired == temp { // equivalent to if tuxedoRequired == nil
print("tuxedoRequired is nil")
}
O segundo ==
operador permite comparar dois opcionais. Ambos precisam ser do mesmo tipo e esse tipo precisa estar em conformidade Equatable
(o protocolo que permite comparar as coisas com o operador "==" regular). Swift (presumivelmente) desembrulha os dois valores e os compara diretamente. Ele também lida com o caso em que um ou ambos os opcionais estão .none
. Observe a distinção entre comparar com o nil
literal.
Além disso, permite comparar qualquer Equatable
tipo com uma embalagem opcional desse tipo:
let numberToFind: Int = 23
let numberFromString: Int? = Int("23") // Optional(23)
if numberToFind == numberFromString {
print("It's a match!") // Prints "It's a match!"
}
Nos bastidores, Swift envolve o não opcional como opcional antes da comparação. Também funciona com literais ( if 23 == numberFromString {
)
Eu disse que existem dois ==
operadores, mas há um terceiro que permite que você coloque nil
no lado esquerdo da comparação
if nil == name { ... }
Opcionais de nomeação
Não existe uma convenção Swift para nomear tipos opcionais de maneira diferente dos tipos não opcionais. As pessoas evitam adicionar algo ao nome para mostrar que é opcional (como "optionalMiddleName" ou "possibleNumberAsString") e permitem que a declaração mostre que é um tipo opcional. Isso fica difícil quando você deseja nomear algo para reter o valor de um opcional. O nome "middleName" implica que é um tipo de String, portanto, quando você extrai o valor da String, geralmente pode acabar com nomes como "actualMiddleName" ou "desempacotadoMiddleName" ou "realMiddleName". Use a ligação opcional e reutilize o nome da variável para contornar isso.
A definição oficial
De "The Basics" na linguagem de programação Swift :
O Swift também introduz tipos opcionais, que tratam da ausência de um valor. Os opcionais dizem "existe um valor e é igual a x" ou "não existe um valor". Os opcionais são semelhantes ao uso nulo com ponteiros no Objective-C, mas funcionam para qualquer tipo, não apenas para classes. Os opcionais são mais seguros e mais expressivos do que os ponteiros nulos no Objective-C e estão no centro de muitos dos recursos mais poderosos do Swift.
Os opcionais são um exemplo do fato de que Swift é um idioma seguro para o tipo. Swift 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. Isso permite capturar e corrigir erros o mais cedo possível no processo de desenvolvimento.
Para terminar, aqui está um poema de 1899 sobre opcionais:
Ontem, na escada
, conheci um homem que não estava lá.
Ele não estava lá hoje novamente
. Desejo, desejo que ele fosse embora.
Antigonish
Mais recursos: