Sobes.tech
Middle

É sempre necessário usar weak ou unowned na lista de captura?

sobes.tech IA

Resposta da IA

Não, nem sempre.

Usamos weak ou unowned na Lista de Captura para evitar ciclos de retenção.

Um ciclo de retenção ocorre quando dois ou mais objetos referenciam-se mutuamente de forma forte, impedindo que a memória seja libertada.

Deve usar-se weak ou unowned quando uma closure faz referência forte a self (ou outra variável de instância de classe/ator), e por sua vez self (ou outro objeto) tem uma referência forte a essa closure.

Exemplos de situações onde isso é necessário:

  • Operações assíncronas (requisições de rede, processamento de dados)
  • Padrão Observer
  • Blocos de animação
  • Filas de despacho

Se a closure não captura self (ou outra variável de classe/ator que tenha uma referência forte à closure), ou captura tipos estruturais/enums, o uso de weak ou unowned não é necessário.

Diferenças entre weak e unowned:

Tipo Descrição Quando usar
weak Referência fraca (opcional). Pode tornar-se nil. Quando o ciclo de vida da closure é mais curto ou igual ao do objeto capturado, e o objeto pode tornar-se nil antes de a closure terminar.
unowned Referência não opcional. Não pode tornar-se nil. Quando o ciclo de vida da closure é mais curto ou igual ao do objeto capturado, e é garantido que o objeto existirá até ao final da closure. Se o objeto for libertado antes da execução da closure, ocorrerá um erro (referência unowned a uma instância desalocada).

Exemplo de uso de weak na Lista de Captura:

class MyClass {
    var name: String = "Test"

    func performAsyncOperation() {
        // A closure captura `self`.
        // MyClass pode ser libertado antes de a operação assíncrona terminar.
        DispatchQueue.global().async { [weak self] in
            guard let self = self else {
                // `self` foi libertado, sair
                return
            }
            print("Operação concluída para \(self.name)")
        }
    }

    deinit {
        print("MyClass desalocado")
    }
}

Exemplo de uso de unowned na Lista de Captura:

class ViewController {
    var dismissButton: UIButton!

    func setupButton() {
        dismissButton.addTarget(self, action: #selector(dismiss), for: .touchUpInside)

        // A closure captura `self`.
        // Suponhamos que `self` (ViewController) vive mais ou igual que a ação do botão.
        // Neste caso, o target-ação não é uma closure, mas ilustra a ideia de `unowned` em ciclos de referência fortes.
        // Se fosse *uma* closure, por exemplo, no manipulador de evento UIControl.Event,
        // e ViewController existe durante o clique:
        // dismissButton.addAction(UIAction { [unowned self] _ in
        //     self.dismiss(animated: true, completion: nil)
        // }, for: .touchUpInside)
    }

    @objc func dismiss() {
        // Fechar o controlador de vista
    }
}

Se a closure não criar ciclo de retenção, o uso de weak ou unowned não é obrigatório e pode até causar verificações desnecessárias (no caso de weak) ou erros potenciais (no caso de unowned).

Por exemplo, uma closure que não captura self ou outro objeto, que tem uma referência forte que a closure possui:

func fetchData(completion: @escaping (Data) -> Void) {
    URLSession.shared.dataTask(with: URL(string: "...")!) { data, response, error in
        guard let data = data else { return }
        // Aqui não há captura de `self` ou outros objetos que possam causar ciclo de retenção.
        completion(data)
    }.resume()
}

Neste caso, [weak self] ou [unowned self] na Lista de Captura não são necessários.