Sobes.tech
Middle

¿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 vuelva nil durante el ciclo de vida de otro objeto. La referencia se vuelve automáticamente nil cuando 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 ser nil.

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.