¿Qué son los ciclos de retención (retain cycles) en iOS?
sobes.tech AI
Respuesta de la IA
Los ciclos de retención ocurren cuando dos o más objetos tienen referencias fuertes entre sí, creando un "círculo cerrado". Como resultado, el contador de referencias de cada objeto nunca llega a cero y no puede ser liberado por ARC (Contador de Referencias Automático). Esto conduce a fugas de memoria, ya que los objetos permanecen en memoria incluso después de dejar de usarlos.
Ejemplo:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) está siendo inicializado")
}
deinit {
print("\(name) está siendo desinicializado")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
print("Apartamento \(unit) está siendo inicializado")
}
deinit {
print("Apartamento \(unit) está siendo desinicializado")
}
}
En este ejemplo, si Person tiene una referencia fuerte a Apartment, y Apartment tiene una referencia fuerte a Person, al intentar liberar estos objetos, no serán eliminados, ya que sus contadores de referencias no alcanzan cero.
Cómo resolver el problema de los ciclos de retención:
Utilice referencias débil (weak) o sin dueño (unowned).
weak: Se usa cuando es posible que el objeto relacionado se vuelvanildurante el ciclo de vida de otro objeto. La referencia se vuelve automáticamentenilcuando el objeto al que apunta se libera. Es opcional.unowned: Se usa cuando se sabe que el objeto relacionado tiene la misma o mayor duración de vida que el objeto que tiene la referencia. No puede sernil.
Ejemplo corregido usando weak:
Cambiemos la referencia tenant en la clase Apartment a débil:
class Apartment {
let unit: String
weak var tenant: Person? // Usamos weak
init(unit: String) {
self.unit = unit
print("Apartamento \(unit) está siendo inicializado")
}
deinit {
print("Apartamento \(unit) está siendo desinicializado")
}
}
Ahora, cuando el objeto Person se libera, la referencia tenant en Apartment se vuelve automáticamente nil, rompiendo el ciclo de retención y permitiendo que Apartment también sea liberado.
Tabla comparativa de weak y unowned:
| Característica | weak |
unowned |
|---|---|---|
| Tipo de referencia | Opcional | No opcional |
| ¿Puede ser nil? | Sí, cuando se libera el objeto | No |
| Cuándo usar | Cuando el objeto puede ser nil |
Cuando el objeto no puede ser nil |
| Rendimiento | Un poco más lento por la comprobación de nil |
Un poco más rápido |
También, los ciclos de retención pueden ocurrir en cierres (closures).
Cuando un cierre captura propiedades de la instancia o self, puede crear una referencia fuerte al objeto. Si ese objeto también tiene una referencia fuerte al cierre (por ejemplo, a través de una propiedad de cierre o delegado), se produce un ciclo de retención.
Cómo resolver los ciclos de retención en cierres:
Utilice lista de captura (capture list).
class SomeClass {
var value = 0
func doSomethingDelayed() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // Lista de captura con weak self
guard let self = self else { return } // Comprobación de nil para weak self
self.value += 1
print("El valor ahora es \(self.value)")
}
}
}
En este ejemplo, [weak self] crea una referencia débil a self dentro del cierre, evitando un ciclo de retención.