Devemos sempre usar [self sem dono] dentro do fechamento no Swift

467

Na sessão 403 da WWDC 2014, Swift intermediário e transcrição , houve o seguinte slide

insira a descrição da imagem aqui

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

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 @IBOutlets como self.temperaturee self.loadingIndicator. Pode ser bom, porque todos os que @IBOutleteu 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
    }
  }
}
Jake Lin
fonte
o link da imagem está quebrado
Daniel Gomez Rico
@ DanielG.R. Obrigado, eu posso ver. i.stack.imgur.com/Jd9Co.png
Jake Lin
2
A menos que eu esteja enganado, o exemplo dado no slide está incorreto - onChangedeve ser um [weak self]fechamento, já que é uma propriedade pública (internamente, mas ainda), para que outro objeto possa obter e armazenar o fechamento, mantendo o objeto TempNotifier por perto (indefinidamente se o objeto using não soltou o onChangefechamento até ver o TempNotifierdesaparecimento, através de sua própria referência fraca para o TempNotifier) . Se var onChange …fosse private var onChange …então [unowned self]estaria correto. Não tenho 100% de certeza disso; alguém me corrija por favor se eu estiver errado.
Slipp D. Thompson
@Jake Lin `var onChange: (Int) -> Void = {}` os chavetas representam um fechamento vazio? mesmo que na definição de uma matriz vazia com []? Não consigo encontrar a explicação nos documentos da Apple.
bibscy
@ bibscy sim, {}é o fechamento vazio (a instância do fechamento) como o padrão (não faz nada), (Int) -> Voidé a definição de fechamento.
Jake Lin

Respostas:

871

Não, definitivamente existem momentos em que você não gostaria de usar [unowned self]. Às vezes, você deseja que o fechamento se capture para garantir que ele ainda esteja disponível quando o fechamento for chamado.

Exemplo: Fazendo uma Solicitação de Rede Assíncrona

Se você estiver fazendo uma solicitação de rede assíncrona , deseja que o fechamento seja retido selfquando a solicitação terminar. Caso contrário, esse objeto pode ter sido desalocado, mas você ainda poderá lidar com o acabamento da solicitação.

Quando usar unowned selfouweak self

O único momento em que você realmente deseja usar [unowned self]ou [weak self]é quando criaria um forte ciclo de referência . Um forte ciclo de referência é quando existe um ciclo de propriedade em que os objetos acabam se possuindo (talvez através de terceiros) e, portanto, nunca serão desalocados, pois ambos garantem que os outros fiquem por perto.

No caso específico de um fechamento, você só precisa perceber que qualquer variável referenciada dentro dele é "propriedade" do fechamento. Enquanto o fechamento estiver próximo, é garantido que esses objetos estejam ao redor. A única maneira de interromper essa propriedade é fazer o [unowned self]ou [weak self]. Portanto, se uma classe possui um fechamento, e esse fechamento captura uma forte referência a essa classe, você tem um forte ciclo de referência entre o fechamento e a classe. Isso também inclui se a classe possui algo que possui o fechamento.

Especificamente no exemplo do vídeo

No exemplo no slide, TempNotifierpossui o fechamento através da onChangevariável de membro. Se eles não declarassem selfcomo unowned, o fechamento também seria responsável por selfcriar um forte ciclo de referência.

Diferença entre unownedeweak

A diferença entre unownede weaké que weaké declarada como opcional enquanto unownednão é. Ao declarar, weakvocê pode lidar com o caso de que ele pode estar nulo dentro do fechamento em algum momento. Se você tentar acessar uma unownedvariável que é nula, ela travará o programa inteiro. Portanto, use somente unownedquando tiver certeza de que a variável sempre estará presente enquanto o fechamento estiver próximo

drewag
fonte
1
Oi. Ótima resposta. Estou lutando para entender o eu sem dono. Um motivo para usar o self fraco simplesmente ser 'self torna-se opcional', não é suficiente para mim. Por que eu especificamente gostaria de usar 'self sem dono' stackoverflow.com/questions/32936264/…
19
@robdashnash, A vantagem de usar o self sem dono é que você não precisa desembrulhar um código opcional que pode ser desnecessário se você souber ao certo por design, que nunca será nulo. Por fim, o eu sem dono é usado por questões de concisão e talvez também como uma dica para futuros desenvolvedores de que você nunca espera um valor nulo.
drewag
77
Um caso para uso [weak self]em uma solicitação de rede assíncrona, está em um controlador de exibição em que essa solicitação é usada para preencher a exibição. Se o usuário voltar, não precisamos mais preencher a visualização, nem precisaremos de uma referência ao controlador de visualização.
David James
1
weakas referências também são definidas para nilquando o objeto é desalocado. unownedreferências não são.
BergQuester 2/11
1
Eu estou um pouco confuso. unownedé usado por non-Optionalenquanto weaké usado para Optionalentão nosso selfé Optionalou non-optional?
Muhammad Nayab
193

Atualização 11/2016

Eu escrevi um artigo sobre isso estendendo esta resposta (procurando no SIL para entender o que o ARC faz), confira aqui .

Resposta original

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

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

Swift fraco vs sem dono

Cenários

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

  1. O fechamento tem o mesmo tempo de vida útil da variável, portanto, o fechamento será alcançável apenas até que a variável seja alcançável . A variável e o fechamento têm a mesma vida útil. Nesse caso, você deve declarar a referência como sem dono . Um exemplo comum é o [unowned self]usado em muitos exemplos de pequenos fechamentos que fazem algo no contexto de seus pais e que não sendo referenciados em nenhum outro lugar não sobrevivem aos 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 estiver mais acessível. Nesse caso, você deve declarar a referência como fraca e verificar se não é nula antes de usá-la (não force a desembrulhar). Um exemplo comum disso é o que [weak delegate]você pode ver em alguns exemplos de fechamento que referenciam um objeto delegado completamente não relacionado (vitalício).

Uso real

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

Citando Joe Groff do twitter :

Sem dono é mais rápido e permite imutabilidade e não-opcionalidade.

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

Você encontrará mais sobre *o funcionamento interno não proprietário aqui .

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

Umberto Raimondi
fonte
26
Estou cansado de ouvir a explicação do papagaio "use semana se o eu puder ser nulo, use sem dono quando nunca puder ser nulo". Ok, entendemos - ouvimos um milhão de vezes! Essa resposta realmente se aprofunda sobre quando o eu pode ser nulo em inglês simples, o que responde diretamente à pergunta do OP. Obrigado por esta ótima explicação !!
TruMan1
Obrigado @ TruMan1, na verdade estou escrevendo um post sobre isso que acabará no meu blog em breve, atualizará a resposta com um link.
Umberto Raimondi
1
Boa resposta, muito prática. Estou inspirado a mudar agora alguns dos meus vars fracos sensíveis ao desempenho para sem dono.
Original_username
"A vida útil do fechamento é independente da da variável" Você tem um erro de digitação aqui?
Mel
1
Se um fechamento sempre tem o mesmo tempo de vida que o objeto pai, a contagem de referência não seria resolvida quando o objeto fosse destruído? Por que você não pode simplesmente usar o 'eu' nessa situação, em vez de se preocupar com quem não é dono ou é fraco?
precisa saber é o seguinte
105

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

  • Se você tiver um fechamento para lidar com uma resposta de uma solicitação de rede weak, use , porque eles têm vida longa. O controlador de exibição pode fechar antes que a solicitação seja concluída, para que selfnão aponte mais para um objeto válido quando o fechamento for chamado.
  • Se você tiver um fechamento que lide com um evento em um botão. Isso unownedocorre porque, assim que o controlador de exibição desaparece, o botão e quaisquer outros itens dos quais ele pode estar se referindo selfdesaparecem ao mesmo tempo. O bloco de fechamento também desaparecerá 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.
     }
possuir
fonte
17
Este precisa de mais votos. Dois exemplos sólidos que mostram como o fechamento de um botão não existirão fora da vida útil do controlador de exibição e, portanto, podem ser usados ​​sem dono, mas a maioria das chamadas de rede que atualizam a interface do usuário precisam ser fracas.
precisa
2
Então, só para esclarecer, sempre usamos sem dono ou fraco ao nos chamarmos de bloqueio de fechamento? Ou há um tempo em que não chamaremos de fraco / sem dono? Se sim, você poderia fornecer um exemplo para isso também?
luke
Muito obrigado.
Shawn Baek
1
Isso me deu um entendimento mais profundo sobre [eu fraco] e [eu sem dono] Muito obrigado @possen!
Tommy
Isso é ótimo. e se eu tiver uma animação baseada na interação do usuário, mas demorar um pouco para terminar. E então o usuário se move para outro viewController. Acho que, nesse caso, eu ainda deveria estar usando, weake não unownedcerto?
Honey
50

Aqui estão citações brilhantes dos Fóruns de desenvolvedores da Apple que descrevem deliciosos detalhes:

unownedvs unowned(safe)vsunowned(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, implicitamente desembrulhada x!sempre que é acessada. unowned(unsafe)é como __unsafe_unretainedno ARC - é uma referência não proprietária, mas não há verificação de tempo de execução que o objeto ainda está ativo no acesso, para que referências pendentes cheguem à memória de lixo. unownedé sempre um sinônimo para o unowned(safe)momento, mas a intenção é que ele seja otimizado unowned(unsafe)nas -Ofast compilações quando as verificações de tempo de execução estiverem desabilitadas.

unowned vs weak

unownedna verdade, usa uma implementação muito mais simples que weak. Os objetos Swift nativos carregam duas contagens de referência e as unowned referências batem na contagem de referência não proprietária em vez da contagem de referência forte . O objeto é desinicializado quando sua contagem de referência forte chega a zero, mas na verdade não é desalocada até que a contagem de referência não proprietária também atinja zero. Isso faz com que a memória seja mantida um pouco mais quando houver referências não proprietárias, mas isso geralmente não é um problema quandounowned é usado porque os objetos relacionados devem ter vidas quase iguais de qualquer maneira, e é muito mais simples e com sobrecarga menor do que a implementação baseada em tabela lateral usada para zerar referências fracas.

Atualização: no Swift moderno, weakinternamente, usa o mesmo mecanismo que o unownedfaz . Portanto, essa comparação está incorreta porque compara Objective-C weakcom Swift unonwed.

Razões

Qual é o objetivo de manter a memória viva depois de possuir referências chegar a 0? O que acontece se o código tentar fazer algo com o objeto usando uma referência não proprietária após sua desinicialização?

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

O que acontece com as referências proprietárias ou não possuídas mantidas pelo objeto? A vida útil deles é dissociada do objeto quando é desinicializada ou a memória também é retida até que o objeto seja desalocado após a liberação da última referência não proprietária?

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

Animado, hein?

Valentin Shergin
fonte
38

Há ótimas respostas aqui. Porém, mudanças recentes na maneira como o Swift implementa referências fracas devem mudar o eu fraco de todos em relação às decisões de uso de si próprias. Anteriormente, se você precisasse do melhor desempenho usando o eu sem dono era superior ao eu fraco, desde que você pudesse ter certeza de que o eu nunca seria nulo, porque acessar o eu sem dono é muito mais rápido do que acessar o eu fraco.

Mas Mike Ash documentou como o Swift atualizou a implementação de vars fracos para usar mesas laterais e como isso melhora substancialmente o fraco desempenho de auto.

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 deveríamos usar isso daqui para frente. O benefício do self fraco é que ele é opcional, o que torna muito mais fácil escrever um código mais correto; é basicamente o motivo pelo qual o Swift é uma linguagem tão boa. Você pode pensar que sabe quais situações são seguras para o uso de si mesmo, mas minha experiência na revisão de muitos outros códigos de desenvolvedores é que a maioria não sabe. Corrigi muitas falhas em que o self sem dono foi desalocado, geralmente em situações em que um thread em segundo plano é concluído depois que um controlador é desalocado.

Erros e falhas são as partes da programação que consomem mais tempo, são mais dolorosas e caras. Faça o seu melhor para escrever o código correto e evite-o. Eu recomendo que seja uma regra nunca forçar desembrulhar os opcionais e nunca usar o eu sem dono em vez do eu fraco. Você não perderá nada perdendo as vezes que forçam o desembrulhamento e o eu sem dono na verdade é seguro. Mas você ganhará muito ao eliminar e encontrar falhas e erros difíceis de encontrar.

SafeFastExpressive
fonte
Obrigado pela atualização e Amém no último parágrafo.
lema
1
Então, após as novas mudanças Existe um momento em weakque não pode ser usado no lugar de um unowned?
Mel
4

De acordo com a Apple-doc

  • Referências fracas são sempre de um tipo opcional e tornam-se automaticamente nulas quando a instância a que se referem é desalocada.

  • Se a referência capturada nunca se tornar nula, ela deve sempre ser capturada como uma referência não proprietária, em vez de uma referência fraca

Exemplo -

    // if my response can nil use  [weak self]
      resource.request().onComplete { [weak self] response in
      guard let strongSelf = self else {
        return
      }
      let model = strongSelf.updateModel(response)
      strongSelf.updateUI(model)
     }

    // Only use [unowned self] unowned if guarantees that response never nil  
      resource.request().onComplete { [unowned self] response in
      let model = self.updateModel(response)
      self.updateUI(model)
     }
Jack
fonte
0

Se nenhuma das opções acima fizer sentido:

tl; dr

Assim como um implicitly unwrapped optional, se você puder garantir que a referência não será nula no ponto de uso, use sem dono. Caso contrário, você deve estar usando fraco.

Explicação:

Eu recuperei o seguinte abaixo em: link não proprietário fraco . Pelo que deduzi, o eu sem dono não pode ser nulo, mas o eu fraco pode ser, e o eu sem dono pode levar a indicadores pendentes ... algo infame no Objective-C. Espero que ajude

"INDEPENDENTE Referências fracas e não-proprietárias se comportam da mesma forma, mas NÃO são iguais."

Referências não proprietárias, como referências fracas, não aumentam a contagem de retenção do objeto que está sendo referido. No entanto, no Swift, uma referência não proprietária tem o benefício adicional de não ser um opcional . Isso os torna mais fáceis de gerenciar, em vez de recorrer ao uso de ligação opcional. Isso não é diferente dos Opcionais Implicitamente Desembrulhados. Além disso, as referências não proprietárias não são zeradas . Isso significa que, quando o objeto é desalocado, ele não zera o ponteiro. Isso significa que o uso de referências não proprietárias pode, em alguns casos, levar a indicadores pendentes. Para vocês nerds que se lembram dos dias de Objective-C como eu, as referências não proprietárias são mapeadas para referências não seguras e sem retenção.

É aqui que fica um pouco confuso.

Referências fracas e não proprietárias, ambas não aumentam as contagens de retenção.

Ambos podem ser usados ​​para interromper os ciclos de retenção. Então, quando os usamos ?!

De acordo com os documentos da Apple :

“Use uma referência fraca sempre que for válido que essa referência se torne nula em algum momento durante sua vida útil. Por outro lado, use uma referência não proprietária quando souber que a referência nunca será nula depois de ter sido definida durante a inicialização. ”

Daksh Gargas
fonte
0
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "AnotherViewController")
        self.navigationController?.pushViewController(controller, animated: true)

    }

}



import UIKit
class AnotherViewController: UIViewController {

    var name : String!

    deinit {
        print("Deint AnotherViewController")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        print(CFGetRetainCount(self))

        /*
            When you test please comment out or vice versa

         */

//        // Should not use unowned here. Because unowned is used where not deallocated. or gurranted object alive. If you immediate click back button app will crash here. Though there will no retain cycles
//        clouser(string: "") { [unowned self] (boolValue)  in
//            self.name = "some"
//        }
//


//
//        // There will be a retain cycle. because viewcontroller has a strong refference to this clouser and as well as clouser (self.name) has a strong refferennce to the viewcontroller. Deint AnotherViewController will not print
//        clouser(string: "") { (boolValue)  in
//            self.name = "some"
//        }
//
//


//        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser (self.name) has a weak refferennce to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)
//
//        clouser(string: "") { [weak self] (boolValue)  in
//            self?.name = "some"
//        }


        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser nos refference to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)

        clouser(string: "") {  (boolValue)  in
            print("some")
            print(CFGetRetainCount(self))

        }

    }


    func clouser(string: String, completion: @escaping (Bool) -> ()) {
        // some heavy task
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            completion(true)
        }

    }

}

Se você não tiver certeza [unowned self] , use [weak self]

Shourob Datta
fonte