Por que criar “Opcionalmente Opcionais sem invólucro”, já que isso implica que você sabe que há um valor?

Por que você criaria uma “opcional implícita não envolvida” ao criar apenas uma variável ou constante regular? Se você sabe que pode ser desembrulhado com sucesso, então por que criar um opcional em primeiro lugar? Por exemplo, por que isso é:

let someString: String! = "this is the string" 

vai ser mais útil que:

 let someString: String = "this is the string" 

Se ”os opcionais indicarem que uma constante ou variável tem permissão para ter ‘sem valor’”, mas “às vezes fica claro da estrutura de um programa que um opcional sempre terá um valor depois que o valor for definido primeiro”, qual é o ponto de tornando-se um opcional em primeiro lugar? Se você sabe que um opcional sempre terá um valor, isso não é opcional?

   

Considere o caso de um object que pode ter propriedades nulas enquanto está sendo construído e configurado, mas é imutável e não-nulo depois (a NSImage é frequentemente tratada dessa maneira, embora no caso dela ainda seja útil sofrer algumas alterações). Opcionais implícitos não desempacotados limpariam bastante seu código, com perda relativamente baixa de segurança (contanto que a garantia fosse válida, seria seguro).

(Editar) Para ser claro, porém: os opcionais regulares são quase sempre preferíveis.

Antes que eu possa descrever os casos de uso para Opcionais Inválidos Sem Envolvimento, você já deve ter entendido o que Opcionais e Opcionais Inválidos Sem Invólucro estão no Swift. Se você não o fizer, eu recomendo que você leia primeiro meu artigo sobre opcionais

Quando usar uma opção implícita não envolvida

Existem duas razões principais para criar uma Opção Implícitamente Desembrulhada. Tudo tem a ver com a definição de uma variável que nunca será acessada quando nil porque, caso contrário, o compilador Swift sempre forçará você a desdobrar explicitamente um Opcional.

1. Uma constante que não pode ser definida durante a boot

Cada constante de membro deve ter um valor no momento em que a boot é concluída. Às vezes, uma constante não pode ser inicializada com seu valor correto durante a boot, mas ainda pode ser garantido que ela tenha um valor antes de ser acessada.

Usar uma variável Opcional contorna esse problema porque um Opcional é inicializado automaticamente com nil e o valor que ele eventualmente conterá ainda será imutável. No entanto, pode ser uma dor estar constantemente desembrulhando uma variável que você sabe com certeza não é nula. Opcionalmente Implicitamente, as opções desempenham os mesmos benefícios que um Opcional, com o benefício adicional de que não é necessário desempacotá-lo explicitamente em todos os lugares.

Um ótimo exemplo disso é quando uma variável de membro não pode ser inicializada em uma subclass UIView até que a view seja carregada:

 class MyView: UIView { @IBOutlet var button: UIButton! var buttonOriginalWidth: CGFloat! override func awakeFromNib() { self.buttonOriginalWidth = self.button.frame.size.width } } 

Aqui, você não pode calcular a largura original do botão até que a exibição seja carregada, mas você sabe que awakeFromNib será chamado antes de qualquer outro método na exibição (diferente da boot). Em vez de forçar o valor a ser explicitamente desdobrado inutilmente em toda a sua class, você pode declará-lo como uma Opção Implicitamente Desembrulhada.

2. Quando seu aplicativo não pode recuperar de uma variável sendo nil

Isso deve ser extremamente raro, mas se seu aplicativo não puder continuar a ser executado se uma variável for nil quando acessada, seria uma perda de tempo incomodar testá-la como nil . Normalmente, se você tiver uma condição absolutamente obrigatória para o aplicativo continuar em execução, use uma assert . Um Opcional Implícitamente Desempacotado tem uma declaração para nil incorporada diretamente nela. Mesmo assim, é sempre bom desdobrar o opcional e usar uma declaração mais descritiva se for nulo.

Quando não usar uma opção implícita não envolvida

1. Variáveis ​​de Membro Calculadas Preguiçosas

Às vezes, você tem uma variável de membro que nunca deve ser nula, mas não pode ser definida para o valor correto durante a boot. Uma solução é usar um Opcional Implícitamente Desempacotado, mas uma maneira melhor é usar uma variável lenta:

 class FileSystemItem { } class Directory : FileSystemItem { lazy var contents : [FileSystemItem] = { var loadedContents = [FileSystemItem]() // load contents and append to loadedContents return loadedContents }() } 

Agora, o contents variável de membro não é inicializado até a primeira vez que é acessado. Isso dá à class uma chance de entrar no estado correto antes de calcular o valor inicial.

Nota: Isto pode parecer contradizer o # 1 de cima. No entanto, há uma distinção importante a ser feita. O buttonOriginalWidth acima deve ser definido durante o viewDidLoad para evitar que alguém altere a largura dos botões antes que a propriedade seja acessada.

2. Em qualquer outro lugar

Na maior parte, Opcionais implicitamente desempacotados devem ser evitados, porque se usados ​​erroneamente, todo o seu aplicativo irá travar quando for acessado enquanto nil . Se você não tiver certeza se uma variável pode ser nula, sempre use o padrão Opcional. Desembrulhar uma variável que nunca é nil certamente não atrapalha muito.

Os opcionais implicitamente não-desembrulhados são úteis para apresentar uma propriedade como não opcional quando, na verdade, ela precisa ser opcional sob as capas. Isso geralmente é necessário para “amarrar o nó” entre dois objects relacionados, cada um precisando de uma referência para o outro. Faz sentido quando nenhuma referência é realmente opcional, mas uma delas precisa ser nula enquanto o par está sendo inicializado.

Por exemplo:

 // These classs are buddies that never go anywhere without each other class B { var name : String weak var myBuddyA : A! init(name : String) { self.name = name } } class A { var name : String var myBuddyB : B init(name : String) { self.name = name myBuddyB = B(name:"\(name)'s buddy B") myBuddyB.myBuddyA = self } } var a = A(name:"Big A") println(a.myBuddyB.name) // prints "Big A's buddy B" 

Qualquer instância B deve ter sempre uma referência myBuddyA válida, portanto, não queremos que o usuário a trate como opcional, mas precisamos que ela seja opcional para que possamos construir um B antes de termos um A para referir.

CONTUDO! Esse tipo de requisito de referência mútua é geralmente uma indicação de acoplamento rígido e design inadequado. Se você se deparar com opções implicitamente não-desembrulhadas, provavelmente deveria considerar a refatoração para eliminar as dependencies cruzadas.

Opcionais implicitamente não-desembrulhados são um compromisso pragmático para tornar o trabalho em ambiente híbrido que precisa interoperar com estruturas Cocoa existentes e suas convenções mais agradáveis, enquanto também permite a migration gradual para um paradigma de programação mais seguro – sem pointers nulos – imposta pelo compilador Swift.

Swift book, no capítulo Noções básicas , seção Opcionalmente Unwrapped Optionals diz:

Opcionais implicitamente não-desembrulhados são úteis quando um valor opcional é confirmado para existir imediatamente depois que o opcional é definido pela primeira vez e pode definitivamente ser assumido como existindo em todos os pontos posteriores. O uso primário de opcionais implicitamente não desembrulhados no Swift é durante a boot da class, conforme descrito em Referências não conhecidas e Propriedades opcionais implacamente não-desempacotadas .

Você pode pensar em um opcional implicitamente não desembrulhado como permissão para que o opcional seja desembrulhado automaticamente sempre que for usado. Em vez de colocar um ponto de exclamação após o nome do opcional toda vez que você usá-lo, coloque um ponto de exclamação após o tipo opcional ao declará-lo.

Isso se resume a casos de uso em que o valor não- nil de propriedades é estabelecido por convenção de uso e não pode ser imposto pelo compilador durante a boot da class. Por exemplo, as propriedades UIViewController que são inicializadas a partir de NIBs ou Storyboards, onde a boot é dividida em fases separadas, mas após o viewDidLoad() você pode assumir que as propriedades geralmente existem. Caso contrário, a fim de satisfazer o compilador, você tinha que estar usando o desembrulhar forçado , encadernação opcional ou encadeamento opcional apenas para obscurecer o propósito principal do código.

A parte acima do livro Swift também se refere ao capítulo Contagem automática de referência :

No entanto, há um terceiro cenário, no qual ambas as propriedades devem sempre ter um valor, e nenhuma propriedade deve ser nil quando a boot for concluída. Nesse cenário, é útil combinar uma propriedade sem proprietário em uma class com uma propriedade opcional implicitamente não-desembrulhada na outra class.

Isso permite que ambas as propriedades sejam acessadas diretamente (sem a opção de desempacotamento opcional) após a conclusão da boot, evitando, ao mesmo tempo, um ciclo de referência.

Isso se resume às peculiaridades de não ser uma linguagem coletada como lixo, portanto, a quebra dos ciclos de retenção está em você como um programador e os opcionais implicitamente não-desembrulhados são uma ferramenta para esconder essa peculiaridade.

Isso abrange a pergunta “Quando usar implicitamente os opcionals não desenrolados em seu código?”. Como desenvolvedor de aplicativos, você encontrará principalmente as assinaturas de método de bibliotecas escritas em Objective-C, que não tem a capacidade de expressar tipos opcionais.

Usando o Swift com Cocoa e Objective-C, seção Trabalhando com nil :

Como o Objective-C não garante que um object seja não-nulo, o Swift torna todas as classs em tipos de argumentos e tipos de retorno opcionais nas APIs do Objective-C importadas. Antes de usar um object Objective-C, verifique se ele não está faltando.

Em alguns casos, você pode estar absolutamente certo de que um método ou propriedade Objective-C nunca retorna uma referência de object nil . Para tornar os objects neste cenário especial mais convenientes para trabalhar, o Swift importa tipos de objects como opcionais implicitamente não desembrulhados . Tipos opcionais implícitos não incluídos incluem todos os resources de segurança dos tipos opcionais. Além disso, você pode acessar o valor diretamente sem verificar se há nil ou desembrulhá-lo por conta própria. Quando você acessa o valor nesse tipo de tipo opcional sem primeiro desempacotá-lo com segurança, o opcional implicitamente não desembrulhado verifica se o valor está ausente. Se o valor estiver faltando, ocorrerá um erro de tempo de execução. Como resultado, você deve sempre verificar e desembrulhar uma opção implicitamente não-desembrulhada, a menos que tenha certeza de que o valor não pode estar faltando.

… e além daqui estava dragões

Exemplos simples de uma linha (ou várias linhas) não cobrem muito bem o comportamento dos opcionais – sim, se você declarar uma variável e fornecer um valor imediatamente, não há sentido em um opcional.

O melhor caso que vi até agora é a configuração que acontece após a boot do object, seguida pelo uso que é “garantido” para seguir essa configuração, por exemplo, em um controlador de visualização:

 class MyViewController: UIViewController { var screenSize: CGSize? override func viewDidLoad { super.viewDidLoad() screenSize = view.frame.size } @IBAction printSize(sender: UIButton) { println("Screen size: \(screenSize!)") } } 

Sabemos que o printSize será chamado depois que a visualização for carregada – é um método de ação conectado a um controle dentro dessa visualização, e nos certificamos de não chamar de outra forma. Assim, podemos nos poupar de alguns opcionais de verificação / binding com o ! . O Swift não reconhece essa garantia (pelo menos até a Apple resolver o problema de parada), então você diz ao compilador que existe.

Isso quebra a segurança do tipo em algum grau, no entanto. Em qualquer lugar em que você tem um opcional implicitamente não desembrulhado, é possível que um aplicativo falhe, caso sua “garantia” não seja válida, por isso é um recurso a ser usado com moderação. Além disso, usando ! o tempo todo faz parecer que você está gritando, e ninguém gosta disso.

A Apple dá um ótimo exemplo na Linguagem de Programação Swift -> Contagem de Referência Automática -> Resolvendo Ciclos de Referência Fortes Entre Instâncias de Classes -> Referências Não Reconhecidas e Propriedades Opcionais Desembrulhadas Implícitas

 class Country { let name: String var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var) init(name: String, capitalName: String) { self.name = name self.capitalCity = City(name: capitalName, country: self) } } class City { let name: String unowned let country: Country init(name: String, country: Country) { self.name = name self.country = country } } 

O inicializador para City é chamado de dentro do inicializador para Country . No entanto, o inicializador do Country não pode passar para o inicializador City até que uma nova instância do Country seja totalmente inicializada, conforme descrito em Inicialização em Duas Fases .

Para lidar com esse requisito, você declara a propriedade capitalCity do Country como uma propriedade opcional implicitamente não-desembrulhada.

A lógica dos opcionais implícitos é mais fácil de explicar olhando primeiro para a lógica da revelação forçada.

Desembrulhar forçado de um opcional (implícito ou não), usando o! operador, significa que você tem certeza de que seu código não tem bugs e o opcional já tem um valor no qual ele está sendo desembrulhado. Sem o! operador, você provavelmente só afirmaria com uma binding opcional:

  if let value = optionalWhichTotallyHasAValue { println("\(value)") } else { assert(false) } 

que não é tão bom quanto

 println("\(value!)") 

Agora, os opcionais implícitos permitem expressar uma opção que você espera sempre ter um valor quando desdobrada, em todos os streams possíveis. Por isso, vai um passo além ao ajudá-lo – relaxando a exigência de escrever o! para desembrulhar a cada vez, e garantir que o tempo de execução ainda irá errar caso suas suposições sobre o stream estejam erradas.

Se você tiver certeza, um retorno de valor de um opcional, em vez de nil , Implicitly Unwrapped Optionals usar para pegar diretamente esses valores de opcionais e não opcionais não pode.

 //Optional string with a value let optionalString: String? = "This is an optional String" //Declaration of an Implicitly Unwrapped Optional String let implicitlyUnwrappedOptionalString: String! //Declaration of a non Optional String let nonOptionalString: String //Here you can catch the value of an optional implicitlyUnwrappedOptionalString = optionalString //Here you can't catch the value of an optional and this will cause an error nonOptionalString = optionalString 

Então esta é a diferença entre o uso de

let someString : String! e let someString : String