Sobes.tech
Middle

Como evitar a criação de um ciclo de retenção (retain cycle) no iOS?

sobes.tech IA

Resposta da IA

Os ciclos de retenção são evitados através do uso correto de referências fracas (weak) ou não retidas incondicionalmente (unowned).

Referências fracas (weak):

  • Não aumentam o contador de referências do objeto.
  • São nulas (podem tornar-se nil).
  • Usadas quando um objeto pode existir independentemente de outro (por exemplo, um delegado).
class DelegateExample {
    weak var delegate: SomeDelegateType? // Referência fraca ao delegado
}

Referências não retidas incondicionalmente (unowned):

  • Não aumentam o contador de referências do objeto.
  • Garantidamente não nulas.
  • Usadas quando um objeto possui explicitamente outro, mas o objeto dependente só pode existir junto com o proprietário (por exemplo, closures capturando self numa classe, onde a closure "pertence" explicitamente à instância da classe). Acesso a uma referência unowned quando o objeto já foi liberado causará um erro em tempo de execução.
class Owner {
    var closure: (() -> Void)?

    func setupClosure() {
        closure = { [unowned self] in // Referência não retida incondicionalmente a self
            // Uso de self
            print("Usando self não retido")
        }
    }
}

Uso em closures: Closures podem criar ciclos de retenção se capturam instâncias de classes por referência forte, e essa instância de classe também contém uma referência forte ao closure. Para evitar isso, usam-se listas de captura (capture lists) no início do closure:

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = { [weak self] in // Referência fraca a self
        guard let strongSelf = self else {
            return "" // Tratamento do caso em que self já é nil
        }
        if let text = strongSelf.text {
            return "<\(strongSelf.name)>\(text)</\(strongSelf.name)>"
        } else {
            return "<\(strongSelf.name)/>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) está sendo desalocado")
    }
}

A escolha entre weak e unowned depende da semântica das relações entre objetos:

  • weak: quando a referência pode tornar-se nil durante o ciclo de vida do objeto referenciado.
  • unowned: quando o objeto referenciado existe garantido por mais tempo ou igual ao objeto que o referencia, e a referência nunca será nil.