Por que criar “Opcionais Implicitamente Desembrulhados”, pois isso implica que você sabe que há um valor?

493

Por que você criaria um "Opcional Implicitamente Desembrulhado" vs criar apenas uma variável ou constante regular? Se você sabe que pode ser desembrulhado com sucesso, por que criar um opcional em primeiro lugar? Por exemplo, por que isso é:

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

será mais útil do que:

let someString: String = "this is the string"

Se ”opcionais indicam que uma constante ou variável pode ter 'nenhum valor'”, mas “às vezes fica claro na estrutura de um programa que um opcional sempre terá um valor depois que esse valor for definido pela primeira vez”, qual é o objetivo de tornando-o opcional em primeiro lugar? Se você sabe que um opcional sempre terá um valor, isso não o torna opcional?

Johnston
fonte

Respostas:

127

Considere o caso de um objeto que pode ter propriedades nulas enquanto está sendo construído e configurado, mas é imutável e não é nulo posteriormente (o NSImage geralmente é tratado dessa maneira, embora ainda seja útil alternar algumas vezes). Opcionais implicitamente desembrulhados limpariam bastante seu código, com perda de segurança relativamente baixa (contanto que a garantia fosse mantida, seria seguro).

(Editar) Para ficar claro: os opcionais regulares são quase sempre preferíveis.

Catfish_Man
fonte
459

Antes que eu possa descrever os casos de uso dos Opcionais Implicitamente Desembrulhados, você já deve entender o que são os Opcionais e os Opcionais Implicitamente Desembrulhados no Swift. Caso contrário, recomendo que você leia primeiro meu artigo sobre opcionais

Quando usar um opcional não implicitamente desembrulhado

Há duas razões principais para criar um Opcional Implicitamente Desembrulhado. Tudo tem a ver com a definição de uma variável que nunca será acessada quando nil, caso contrário, o compilador Swift sempre o forçará a desembrulhar explicitamente um opcional.

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

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

O uso de uma variável Opcional contorna esse problema porque um Opcional é inicializado automaticamente nile o valor que eventualmente conterá ainda será imutável. No entanto, pode ser uma tarefa difícil desembrulhar constantemente uma variável que você sabe com certeza não é nula. Opcionais implicitamente desembrulhados alcançam os mesmos benefícios que um opcional com o benefício adicional de que não é necessário desembrulhá-lo explicitamente em todos os lugares.

Um ótimo exemplo disso é quando uma variável de membro não pode ser inicializada em uma subclasse UIView até que a visualização 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 awakeFromNibisso será chamado antes de qualquer outro método na exibição (que não seja a inicialização). Em vez de forçar o valor a ser explicitamente desembrulhado sem sentido em toda a sua classe, você pode declará-lo como um Opcional Implicitamente Desembrulhado.

2. Quando seu aplicativo não pode se recuperar de um ser variável nil

Isso deve ser extremamente raro, mas se o aplicativo não puder continuar em execução se uma variável for nilacessada, seria um desperdício de tempo se preocupar em testá-lo nil. Normalmente, se você tem uma condição que deve ser absolutamente verdadeira para que seu aplicativo continue em execução, use um assert. Um opcional não implicitamente desembrulhado possui uma afirmação de nada embutida nele. Mesmo assim, geralmente é bom desembrulhar o opcional e usar uma afirmação mais descritiva, se for nulo.

Quando não usar um opcional implicitamente desembrulhado

1. Variáveis ​​de membro calculadas preguiçosamente

Às vezes, você tem uma variável de membro que nunca deve ser nula, mas não pode ser definida com o valor correto durante a inicialização. Uma solução é usar um opcional implicitamente desembrulhado, mas a melhor maneira é 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, a variável de membro contentsnão é inicializada até a primeira vez que é acessada. Isso dá à classe a chance de entrar no estado correto antes de calcular o valor inicial.

Nota: Isso pode parecer contradizer o número 1 acima. No entanto, há uma distinção importante a ser feita. O buttonOriginalWidthacima deve ser definido durante o viewDidLoad para impedir que alguém altere a largura dos botões antes que a propriedade seja acessada.

2. Em todo lugar

Na maioria das vezes, os Opcionais Implicitamente Desembrulhados devem ser evitados porque, se usado incorretamente, todo o seu aplicativo falhará quando for acessado enquanto nil. Se você nunca tiver certeza se uma variável pode ser nula, sempre use o Opcional normal. Desembrulhar uma variável que nunca é nilcertamente não dói muito.

drewag
fonte
4
Esta resposta deve ser atualizada para a versão 5. Você não pode mais usar if someOptional.
Papai Noel
2
O @SantaClaus hasValueestá definido no Optional. Eu prefiro a semântica do que hasValuea de != nil. Eu sinto que é muito mais compreensível para novos programadores que não usaram nilem outros idiomas. hasValueé muito mais lógico que nil.
drewag
2
Looks como hasValuefoi retirado beta 6. Ash colocá-lo novamente embora ... github.com/AshFurrow/hasValue
Chris Wagner
1
@newacct Em relação ao tipo de retorno dos inicializadores Objc, é mais um opcional implícito não desempacotado implícito. O comportamento que você descreveu para usar um "não opcional" é exatamente o que um Opcional Implicitamente Desembrulhado faria (não falha até ser acessado). A respeito de deixar o programa falhar mais cedo ao desembrulhar à força, concordo que o uso de um não opcional é o preferido, mas isso nem sempre é possível.
drewag 1/09/09
1
@confile no. Não importa o quê, ele aparecerá no Objective-C como um ponteiro (se fosse opcional, implicitamente desembrulhado ou não opcional).
drewag
56

Opcionais implicitamente desembrulhados são úteis para apresentar uma propriedade como não opcional quando realmente precisa ser opcional sob as cobertas. Isso geralmente é necessário para "dar um nó" entre dois objetos relacionados, que precisam 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 classes 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 Binstância deve sempre ter uma myBuddyAreferência válida ; portanto, não queremos que o usuário a trate como opcional, mas precisamos que ela seja opcional para que possamos construir a Bantes de termos uma Areferência.

CONTUDO! Esse tipo de requisito de referência mútua geralmente é uma indicação de acoplamento rígido e design deficiente. Se você se deparar com opcionais implicitamente desembrulhados, provavelmente deverá considerar a refatoração para eliminar as dependências cruzadas.

n8gray
fonte
7
Eu acho que uma das razões que criaram esse recurso de linguagem é@IBOutlet
Jiaaro
11
+1 para a ressalva "NO ENTANTO". Pode não ser verdade o tempo todo, mas certamente é algo a se observar.
JMD #
4
Você ainda tem um forte ciclo de referência entre A e B. Os opcionais implicitamente desembrulhados NÃO criam uma referência fraca. Você ainda precisa declarar myByddyA ou myBuddyB como fraco (provavelmente myBuddyA)
drewag
6
Para ficar ainda mais claro por que essa resposta está errada e perigosamente enganosa: Os acessórios implicitamente desembrulhados não têm absolutamente nada a ver com o gerenciamento de memória e não impedem os ciclos de retenção. No entanto, os opcionais implicitamente desembrulhados ainda são úteis na circunstância descrita para configurar a referência bidirecional. Então, simplesmente adicionando a weakdeclaração e remover "sem criar uma forte reter ciclo"
drewag
1
@drewag: Você está certo - editei a resposta para remover o ciclo de retenção. Eu pretendia fazer com que a referência anterior fosse fraca, mas acho que isso me escapou da cabeça.
N8gray
37

Os opcionais implícitamente desembrulhados são um compromisso pragmático para tornar o trabalho em ambiente híbrido que deve interoperar com as estruturas existentes do Cocoa e suas convenções mais agradáveis, além de permitir a migração gradual para um paradigma de programação mais seguro - sem ponteiros nulos - imposto pelo compilador Swift.

O livro Swift, no capítulo Básico , na seção Implicitamente Desembrulhado Opcional, diz:

Opcionais implicitamente desembrulhados são úteis quando é confirmado que o valor de um opcional existe imediatamente após o primeiro ser definido pela primeira vez e, definitivamente, pode-se presumir que existe em todos os pontos posteriores. O uso principal de opcionais implicitamente desembrulhados no Swift é durante a inicialização da classe, conforme descrito em Referências não proprietárias e Propriedades opcionais implicitamente desembrulhadas .

Você pode pensar em um opcional implicitamente desembrulhado como dando 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 sempre que você o usar, coloque um ponto de exclamação após o tipo do opcional quando o declarar.

Isso se aplica aos casos de uso em que a não- nilintegridade das propriedades é estabelecida por meio da convenção de uso e não pode ser imposta pelo compilador durante a inicialização da classe. Por exemplo, as UIViewControllerpropriedades que são inicializadas a partir de NIBs ou Storyboards, onde a inicialização é dividida em fases separadas, mas após viewDidLoad()você pode assumir que as propriedades geralmente existem. Caso contrário, para satisfazer o compilador, era necessário usar o desempacotamento forçado , a ligação opcional ou o encadeamento opcional apenas para ocultar o objetivo principal do código.

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

No entanto, existe um terceiro cenário, no qual ambas as propriedades devem sempre ter um valor e nenhuma deve ser nilassim que a inicialização estiver concluída. Nesse cenário, é útil combinar uma propriedade não proprietária em uma classe com uma propriedade opcional implicitamente desembrulhada na outra classe.

Isso permite que ambas as propriedades sejam acessadas diretamente (sem desembrulhar opcional) quando a inicialização estiver concluída, evitando um ciclo de referência.

Isso se resume às peculiaridades de não ser uma linguagem de coleta de lixo; portanto, a interrupção dos ciclos de retenção é uma tarefa do programador e os opcionais implicitamente desembrulhados são uma ferramenta para ocultar essa peculiaridade.

Isso abrange os itens "Quando usar opcionais implicitamente desembrulhados no seu código?" questão. Como desenvolvedor de aplicativos, você os encontrará principalmente em assinaturas de método de bibliotecas escritas em Objective-C, que não têm a capacidade de expressar tipos opcionais.

Em Usando Swift com cacau e Objective-C, seção Trabalhando com zero :

Como o Objective-C não garante que um objeto seja nulo, o Swift torna todas as classes nos tipos de argumento e os tipos de retorno opcionais nas APIs do Objective-C importadas. Antes de usar um objeto 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 nilreferência a objeto. Para tornar os objetos nesse cenário especial mais convenientes para trabalhar, o Swift importa tipos de objetos como opcionais implicitamente desembrulhados . Tipos opcionais implicitamente desembrulhados incluem todos os recursos de segurança de tipos opcionais. Além disso, você pode acessar o valor diretamente sem verificar se hánilou desembrulhando você mesmo. Quando você acessa o valor nesse tipo de tipo opcional sem o desembrulhar com segurança primeiro, o opcional implicitamente desembrulhado verifica se o valor está ausente. Se o valor estiver ausente, ocorrerá um erro de tempo de execução. Como resultado, você sempre deve verificar e desembrulhar um opcional implicitamente desembrulhado, a menos que tenha certeza de que o valor não pode estar ausente.

... e além daqui jazia dragões

Palimondo
fonte
Obrigado por esta resposta completa. Você consegue pensar em uma lista de verificação rápida de quando usar um Opcional Implicitamente Desembrulhado e quando uma variável padrão seria suficiente?
#
@Hairgami_Master eu adicionei a minha própria resposta com uma lista e exemplos concretos
drewag
18

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 eu vi até agora é a configuração que ocorre após a inicialização do objeto, seguida pelo uso "garantido" para seguir essa configuração, por exemplo, em um controlador de exibiçã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 printSizeque será chamado depois que a exibição for carregada - é um método de ação conectado a um controle dentro dessa exibição e tomamos o cuidado de não chamá-lo de outra forma. Para que possamos economizar algumas verificações / ligações opcionais com o !. Swift não pode reconhecer essa garantia (pelo menos até que a Apple resolva o problema de parada), então você diz ao compilador que ela existe.

Isso interrompe a segurança do tipo em algum grau, no entanto. Qualquer lugar em que você tenha um opcional implicitamente desembrulhado é um local em que seu aplicativo pode travar se a sua "garantia" nem sempre for válida, portanto, é um recurso a ser usado com moderação. Além disso, usar !o tempo todo faz parecer que você está gritando, e ninguém gosta disso.

rickster
fonte
Por que não apenas inicializar screenSize para CGSize (height: 0, width: 0) e economizar o problema de ter que gritar a variável toda vez que você a acessa?
Martin Gordon
Um tamanho pode não ter sido o melhor exemplo, pois CGSizeZeropode ser um bom valor de sentinela no uso no mundo real. Mas e se você tiver um tamanho carregado da ponta que possa realmente ser zero? Então, usar CGSizeZerocomo sentinela não ajuda a distinguir entre um valor não definido e definido como zero. Além disso, esta aplica-se igualmente bem a outros tipos carregados de bico (ou em qualquer outro lugar depois init): cordas, as referências a subvisualizações, etc.
Rickster
2
Parte do ponto de Opcional em linguagens funcionais é não ter valores de sentinela. Você tem um valor ou não. Você não deve ter um caso em que tenha um valor que indique um valor ausente.
Wayne Tanner
4
Acho que você não entendeu a pergunta do OP. O OP não está perguntando sobre o caso geral de opcionais, mas especificamente sobre a necessidade / uso de opcionais implicitamente desembrulhados (ou seja let foo? = 42, não let foo! = 42). Isso não resolve isso. (Isto pode ser uma resposta relevante sobre opcionais, você mente, mas não opcionais sobre implicitamente desembrulhados que são um / animal relacionado diferente.)
JMD
15

A Apple fornece um excelente exemplo em The Swift Programming Language -> Contagem automática de referências -> Resolvendo ciclos de referência fortes entre instâncias de classe -> Referências não proprietárias e propriedades opcionais implicitamente desembrulhadas

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 de Cityé chamado de dentro do inicializador de Country. No entanto, o inicializador para Countrynão pode passar selfpara o Cityinicializador até que uma nova Countryinstância seja totalmente inicializada, conforme descrito em Inicialização em duas fases .

Para lidar com esse requisito, você declara a capitalCitypropriedade Countrycomo uma propriedade opcional implicitamente desembrulhada.

fujianjin6471
fonte
O tutorial mencionado nesta resposta está aqui .
Franklin Yu
3

A lógica dos opcionais implícitos é mais fácil de explicar, olhando primeiro a lógica do desembrulhamento forçado.

Desembrulhamento 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 possui bugs e o opcional já possui um valor em que está sendo desembrulhado. Sem o! operador, você provavelmente apenas afirmaria com uma ligação opcional:

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

o que não é tão bom quanto

println("\(value!)")

Agora, os opcionais implícitos permitem expressar um opcional que você sempre espera ter um valor quando desembrulhado, em todos os fluxos possíveis. Por isso, apenas vai um passo além em ajudá-lo - relaxando a exigência de escrever o! para desembrulhar a cada vez e garantir que o tempo de execução ainda erro, caso suas suposições sobre o fluxo estejam incorretas.

Danra
fonte
1
@ newacct: nulo em todos os fluxos possíveis através do seu código não é o mesmo que nulo da inicialização pai (classe / estrutura) por desalocação. O Interface Builder é o exemplo clássico (mas existem muitos outros padrões de inicialização atrasada): se uma classe for usada apenas a partir de uma ponta, as variáveis ​​de saída não serão definidas init(as quais você talvez nem implemente), mas elas ' é garantido que será definido após awakeFromNib/ viewDidLoad.
Rickster 23/05
3

Se você tiver certeza, um valor retornado por um opcional, em vez de nil, Opcionais Implicitamente Desembrulhados usa para capturar diretamente esses valores de opcionais e não opcionais.

//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

Portanto, esta é a diferença entre o uso de

let someString : String! e let someString : String

enadun
fonte
1
Isso não responde à pergunta do OP. O OP sabe o que é um Opcional Implicitamente Desembrulhado.
Franklin Yu
0

Eu acho que Optionalé um nome ruim para essa construção que confunde muitos iniciantes.

Outros idiomas (Kotlin e C # por exemplo) usam o termo Nullablee facilita muito a compreensão disso.

Nullablesignifica que você pode atribuir um valor nulo a uma variável desse tipo. Portanto, se for Nullable<SomeClassType>, você pode atribuir nulos a ele, se for apenas SomeClassType, você não pode. É assim que o Swift funciona.

Por que usá-los? Bem, às vezes você precisa ter nulos, é por isso. Por exemplo, quando você sabe que deseja ter um campo em uma classe, mas não pode atribuí-lo a nada quando estiver criando uma instância dessa classe, mas o fará posteriormente. Não vou dar exemplos, porque as pessoas já os forneceram aqui. Só estou escrevendo isso para dar meus 2 centavos.

A propósito, sugiro que você veja como isso funciona em outros idiomas, como Kotlin e C #.

Aqui está um link que explica esse recurso no Kotlin: https://kotlinlang.org/docs/reference/null-safety.html

Outras linguagens, como Java e Scala, têm Optionals, mas funcionam de forma diferente de Optionals no Swift, porque os tipos de Java e Scala são todos anuláveis ​​por padrão.

Em suma, acho que esse recurso deveria ter sido nomeado Nullableem Swift, e não Optional...

ei você
fonte
0

Implicitly Unwrapped Optionalé um açúcar sintático, pois Optionalnão força um programador a desembrulhar uma variável. Pode ser usado para uma variável que não pode ser inicializada durante two-phase initialization processe implica um valor nulo. Essa variável se comporta como não nula, mas na verdade é uma variável opcional . Um bom exemplo é - as tomadas do Interface Builder

Optional geralmente são preferíveis

var nonNil: String = ""
var optional: String?
var implicitlyUnwrappedOptional: String!

func foo() {
    //get a value
    nonNil.count
    optional?.count

    //Danderour - makes a force unwrapping which can throw a runtime error
    implicitlyUnwrappedOptional.count

    //assign to nil
//        nonNil = nil //Compile error - 'nil' cannot be assigned to type 'String'
    optional = nil
    implicitlyUnwrappedOptional = nil
}
yoAlex5
fonte