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 tornenildurante o ciclo de vida de outro objeto. A referência torna-se automaticamentenilquando 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 sernil.
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.