Sobes.tech
Middle

¿Cómo se puede prevenir la creación de un ciclo de retención (retain cycle) en iOS?

sobes.tech AI

Respuesta de la IA

Los ciclos de retención se previenen mediante el uso correcto de referencias débiles (weak) o no retenedoras incondicionales (unowned).

Referencias débiles (weak):

  • No aumentan el contador de referencias del objeto.
  • Son nulas (pueden volverse nil).
  • Se usan cuando un objeto puede existir independientemente de otro (por ejemplo, un delegado).
class DelegateExample {
    weak var delegate: SomeDelegateType? // Referencia débil al delegado
}

Referencias no retenedoras incondicionales (unowned):

  • No aumentan el contador de referencias del objeto.
  • Garantizadas no nulas.
  • Se usan cuando un objeto posee explícitamente a otro, pero el objeto dependiente solo puede existir junto con el propietario (por ejemplo, cierres que capturan self en una clase, donde el cierre "pertenece" explícitamente a la instancia de la clase). Acceder a una referencia unowned cuando el objeto ya ha sido liberado causará un error en tiempo de ejecución.
class Owner {
    var closure: (() -> Void)?

    func setupClosure() {
        closure = { [unowned self] in // Referencia no retenedora incondicional a self
            // Uso de self
            print("Usando self no retenedor")
        }
    }
}

Uso en cierres: Los cierres pueden crear ciclos de retención si capturan instancias de clases mediante referencias fuertes, y esa instancia de clase también contiene una referencia fuerte al cierre. Para evitarlo, se usan listas de captura (capture lists) al inicio del cierre:

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

    lazy var asHTML: () -> String = { [weak self] in // Referencia débil a self
        guard let strongSelf = self else {
            return "" // Manejo del caso cuando self ya es 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á siendo destruido")
    }
}

La elección entre weak y unowned depende de la semántica de las relaciones entre objetos:

  • weak: cuando la referencia puede volverse nil durante el ciclo de vida del objeto al que se refiere.
  • unowned: cuando el objeto referenciado existe garantizado por más tiempo o igual que el objeto que lo referencia, y la referencia nunca será nil.