Sobes.tech
Middle

O que são ciclos de retenção (retain cycles) no iOS?

sobes.tech IA

Resposta da IA

Os ciclos de retenção ocorrem quando dois ou mais objetos têm referências fortes entre si, criando um "círculo fechado". Como resultado, o contador de referências de cada objeto nunca chega a zero e não pode ser libertado pelo ARC (Contagem Automática de Referências). Isto leva a fugas de memória, pois os objetos permanecem na memória mesmo após deixarem de ser utilizados.

Exemplo:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) está a ser inicializado")
    }

    deinit {
        print("\(name) está a ser desinicializado")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?

    init(unit: String) {
        self.unit = unit
        print("Apartamento \(unit) está a ser inicializado")
    }

    deinit {
        print("Apartamento \(unit) está a ser desinicializado")
    }
}

Neste exemplo, se Person tiver uma referência forte a Apartment, e Apartment tiver uma referência forte a Person, ao tentar libertar estes objetos, eles não serão eliminados, pois os seus contadores de referências não atingem zero.

Como resolver o problema dos ciclos de retenção:

Utilize referências fracas (weak) ou sem dono (unowned).

  • weak: Usado quando é possível que o objeto relacionado se torne nil durante o ciclo de vida de outro objeto. A referência torna-se automaticamente nil quando o objeto ao qual se refere é libertado. É opcional.
  • unowned: Usado quando se sabe que o objeto relacionado tem a mesma ou maior duração de vida do que o objeto que tem a referência. Não pode ser nil.

Exemplo corrigido usando weak:

Vamos alterar a referência tenant na classe Apartment para fraca:

class Apartment {
    let unit: String
    weak var tenant: Person? // Usamos weak

    init(unit: String) {
        self.unit = unit
        print("Apartamento \(unit) está a ser inicializado")
    }

    deinit {
        print("Apartamento \(unit) está a ser desinicializado")
    }
}

Agora, quando o objeto Person for libertado, a referência tenant em Apartment torna-se automaticamente nil, quebrando o ciclo de retenção e permitindo que Apartment também seja libertado.

Tabela de comparação de weak e unowned:

Característica weak unowned
Tipo de referência Opcional Não opcional
Pode ser nil? Sim, quando o objeto é libertado Não
Quando usar Quando o objeto pode ser nil Quando se sabe que o objeto não será nil
Performance Um pouco mais lento devido à verificação de nil Um pouco mais rápido

Além disso, ciclos de retenção podem ocorrer em closures.

Quando uma closure captura propriedades de uma instância ou self, ela pode criar uma referência forte ao objeto. Se esse objeto também tiver uma referência forte à closure (por exemplo, através de uma propriedade de closure ou delegado), ocorre um ciclo de retenção.

Como resolver os ciclos de retenção em closures:

Utilize lista de captura (capture list).

class SomeClass {
    var value = 0

    func doSomethingDelayed() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // Lista de captura com weak self
            guard let self = self else { return } // Verificação de nil para weak self
            self.value += 1
            print("O valor agora é \(self.value)")
        }
    }
}

Neste exemplo, [weak self] cria uma referência fraca a self dentro do closure, evitando um ciclo de retenção.