ios - unowned - when to use weak self




Devemos sempre usar[self self-self] dentro do fechamento em Swift (5)

Atualização 11/2016

Eu escrevi um artigo sobre isso estendendo essa resposta (olhando para SIL para entender o que a ARC faz), confira here .

Resposta original

As respostas anteriores não fornecem regras diretas sobre quando usar uma sobre a outra e por que, então deixe-me adicionar algumas coisas.

A discussão sem dono ou fraca se resume a uma questão de tempo de vida da variável e do fechamento que a referencia.

Cenários

Você pode ter dois cenários possíveis:

  1. O fechamento tem o mesmo tempo de vida da variável, portanto, o fechamento será acessível somente até que a variável seja alcançável . A variável e o fechamento têm a mesma duração. Nesse caso, você deve declarar a referência como sem dono . Um exemplo comum é o [unowned self] usado em muitos exemplos de pequenos encerramentos que fazem algo no contexto de seus pais e que não sendo referenciados em nenhum outro lugar não sobrevivem aos seus pais.

  2. O tempo de vida do fechamento é independente do da variável, o fechamento ainda pode ser referenciado quando a variável não está mais acessível. Neste caso, você deve declarar a referência como fraca e verificar se ela não é nula antes de usá-la (não force o desembrulhar). Um exemplo comum disso é o [weak delegate] você pode ver em alguns exemplos de fechamento referenciando um objeto delegado completamente não relacionado (vitalício).

Uso Real

Então, qual será / você deve usar na maioria das vezes?

Citando Joe Groff do twitter :

Unowned é mais rápido e permite imutabilidade e nonoptionality.

Se você não precisa de fraco, não o use.

Você encontrará mais sobre o funcionamento interno sem dono * here .

* Geralmente também é chamado de não proprietário (seguro) para indicar que as verificações de tempo de execução (que levam a uma falha por referências inválidas) são executadas antes de acessar a referência sem proprietário.

Na sessão WWDC 2014 403 Intermediate Swift e transcript , houve o seguinte slide

O orador disse nesse caso, se não usarmos [unowned self] lá, será um vazamento de memória. Isso significa que devemos sempre usar [unowned self] dentro do fechamento?

Na linha 64 do ViewController.swift do aplicativo Swift Weather , eu não uso [unowned self] . Mas eu atualizo a interface do usuário usando alguns @IBOutlet s como self.temperature e self.loadingIndicator . Pode ser OK porque todos os @IBOutlet s que eu defini são weak . Mas por segurança, devemos sempre usar [unowned self] ?

class TempNotifier {
  var onChange: (Int) -> Void = {_ in }
  var currentTemp = 72
  init() {
    onChange = { [unowned self] temp in
      self.currentTemp = temp
    }
  }
}

Aqui estão citações brilhantes de fóruns de desenvolvedores da Apple descritos detalhes deliciosos:

unowned vs unowned(safe) vs unowned(unsafe)

unowned(safe) é uma referência não proprietária que afirma no acesso que o objeto ainda está ativo. É como uma referência opcional fraca que é implicitamente desembrulhada com x! toda vez que é acessado. unowned(unsafe) é como __unsafe_unretained no ARC - é uma referência não proprietária, mas não há nenhuma verificação em tempo de execução de que o objeto ainda está ativo no acesso, portanto, referências pendentes atingirão a memória do lixo. unowned é sempre um sinônimo para unowned(safe) atualmente, mas a intenção é que ele seja otimizado para unowned(unsafe) em builds -Ofast quando as verificações de tempo de execução estiverem desabilitadas.

unowned vs weak

unowned na verdade usa uma implementação muito mais simples que weak . Objetos Nativo Swift carregam duas contagens de referência e referências unowned batem a contagem de referência sem proprietário em vez da contagem de referência forte . O objeto é desinicializado quando sua contagem de referência forte atinge zero, mas não é realmente desalocada até que a contagem de referência sem proprietário também atinja zero. Isso faz com que a memória seja mantida um pouco mais longa quando há referências sem dono, mas isso geralmente não é um problema quando unowned é usado porque os objetos relacionados devem ter vida útil quase igual, e é muito mais simples e mais baixa que a implementação baseada em tabela lateral usada para zerar referências fracas.

Atualização: No Swift moderno, o weak usa internamente o mesmo mecanismo que o unowned faz . Portanto, esta comparação está incorreta porque compara o Objective-C weak com o Swift unonwed .

Razões

Qual é o propósito de manter a memória ativa depois de possuir referências? O que acontece se o código tentar fazer alguma coisa com o objeto usando uma referência não dona depois de ser desinicializada?

A memória é mantida viva para que suas contagens de retenção ainda estejam disponíveis. Dessa forma, quando alguém tenta manter uma referência forte ao objeto sem dono, o tempo de execução pode verificar se a contagem de referência forte é maior que zero, a fim de garantir que seja seguro reter o objeto.

O que acontece com as referências proprietárias ou não possuídas pelo objeto? A vida deles é desacoplada do objeto quando é desinicializada ou sua memória também é retida até que o objeto seja desalocado após a última referência sem proprietário ser liberada?

Todos os recursos pertencentes ao objeto são liberados assim que a última referência forte do objeto é liberada e seu deinit é executado. Referências sem dono mantêm a memória ativa - além do cabeçalho com as contagens de referência, seu conteúdo é lixo.

Animado, né?


Eu pensei em adicionar alguns exemplos concretos especificamente para um controlador de visualização. Muitas das explicações, não apenas aqui no , são realmente boas, mas eu trabalho melhor com exemplos do mundo real (@drewag teve um bom começo sobre isso):

  • Se você tiver um fechamento para manipular uma resposta de uma rede, use as solicitações weak , porque elas são de longa duração. O controlador de visualização pode fechar antes que a solicitação seja concluída, portanto, o self não aponta mais para um objeto válido quando o encerramento é chamado.
  • Se você tiver um fechamento que manipule um evento em um botão. Isso pode ser unowned porque, assim que o controlador de visualização for embora, o botão e quaisquer outros itens que possam estar fazendo referência de self desaparecerão ao mesmo tempo. O bloco de fechamento também desaparece ao mesmo tempo.

    class MyViewController: UIViewController {
          @IBOutlet weak var myButton: UIButton!
          let networkManager = NetworkManager()
          let buttonPressClosure: () -> Void // closure must be held in this class. 
    
          override func viewDidLoad() {
              // use unowned here
              buttonPressClosure = { [unowned self] in
                  self.changeDisplayViewMode() // won't happen after vc closes. 
              }
              // use weak here
              networkManager.fetch(query: query) { [weak self] (results, error) in
                  self?.updateUI() // could be called any time after vc closes
              }
          }
          @IBAction func buttonPress(self: Any) {
             buttonPressClosure()
          }
    
          // rest of class below.
     }

Existem algumas ótimas respostas aqui. Mas mudanças recentes em como o Swift implementa referências fracas devem mudar as decisões de self self versus self self use. Anteriormente, se você precisasse do melhor desempenho, usar self sem dono era superior ao self fraco, contanto que você pudesse ter certeza de que o self nunca seria nulo, porque acessar o self sem dono é muito mais rápido do que acessar o self fraco.

Mas Mike Ash documentou como o Swift atualizou a implementação de vars fracos para usar tabelas laterais e como isso melhora substancialmente o baixo desempenho do usuário.

https://mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html

Agora que não há uma penalidade de desempenho significativa para o eu fraco, acredito que devemos usar o padrão para ir adiante. O benefício do self fraco é que ele é opcional, o que torna muito mais fácil escrever código mais correto, é basicamente a razão pela qual o Swift é uma ótima linguagem. Você pode pensar que sabe quais situações são seguras para o uso de self sem dono, mas a minha experiência de revisar muitos outros códigos de desenvolvedores é, a maioria não. Eu consertei muitos crashes em que self não-proprietário foi desalocado, geralmente em situações em que um encadeamento de background é concluído depois que um controlador é desalocado.

Bugs e falhas são as partes mais demoradas, dolorosas e caras da programação. Faça o seu melhor para escrever o código correto e evitá-los. Eu recomendo fazer uma regra para nunca forçar o desempacotamento de opcionais e nunca usar self sem proprietário em vez de self fraco. Você não perderá nada perdendo as vezes que a força desembrulhando e sem dono realmente está segura. Mas você vai ganhar muito com a eliminação de hard to find e debug crashes and bugs.






automatic-ref-counting