Sobes.tech
Middle

¿Cómo funciona la gestión automática de memoria (ARC) en Swift?

sobes.tech AI

Respuesta de la IA

ARC (Contador de Referencias Automático) en Swift rastrea y gestiona el uso de memoria de tus aplicaciones. Libera automáticamente la memoria utilizada por las instancias de clases cuando ya no son necesarias, previniendo fugas de memoria.

Al crear una nueva instancia de una clase, ARC asigna un bloque de memoria para almacenar información sobre esa instancia. Cuando la instancia ya no es necesaria, ARC libera esa memoria para que pueda ser utilizada por otros propósitos.

ARC funciona contando el número de referencias fuertes (strong references) a cada instancia de clase. Una referencia fuerte es aquella que mantiene la instancia en memoria. Cuando el número de referencias fuertes a una instancia llega a cero, ARC libera la memoria asociada.

Existen tres tipos de referencias en Swift que afectan a ARC:

  1. Referencias Fuertes (Strong References): Aumentan el contador de referencias a la instancia.
  2. Referencias Débiles (Weak References): No aumentan el contador de referencias. Se usan para evitar ciclos de referencias fuertes, cuando dos instancias de clases mantienen referencias fuertes entre sí. La referencia débil se vuelve automáticamente nil al desinicializarse el objeto al que apunta.
  3. Referencias No Propietarias (Unowned References): Tampoco aumentan el contador de referencias. Se usan cuando estás seguro de que la instancia a la que se refiere siempre existirá mientras exista la instancia actual. No pueden ser opcionales (nil). Intentar acceder a un objeto desinicializado a través de una referencia no propietaria causará un error en tiempo de ejecución.

Ejemplo de ciclo de referencias fuertes:

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")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil // Person no se desinicializa
unit4A = nil // Apartment no se desinicializa

Para resolver el ciclo, usa referencias débiles o no propietarias. En este caso, tenant en Apartment debería ser una referencia débil:

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
    weak var tenant: Person? // Uso de referencia débil

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

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

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john

john = nil // Person se desinicializa
unit4A = nil // Apartment se desinicializa

La elección entre referencia débil y no propietaria depende de la relación entre objetos:

  • Débil: Si uno de los objetos puede volverse nil antes de que el otro se desinicialice (valor opcional).
  • No propietario: Si uno de los objetos siempre existe mientras exista el otro (valor no opcional).

ARC también gestiona la memoria utilizada por cierres (closures), que capturan instancias de clases. Los ciclos de referencias fuertes pueden ocurrir cuando un cierre captura una instancia de clase y esa instancia tiene una referencia fuerte al cierre. Para evitarlo, se usan listas de captura (capture lists) en los cierres.

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

    lazy var asHTML: () -> String = {
        // Sin lista de captura, posible ciclo fuerte
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

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

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hola, mundo")
print(paragraph!.asHTML())

paragraph = nil // HTMLElement no se desinicializa

Solución usando lista de captura [weak self]:

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

    lazy var asHTML: () -> String = { [weak self] in // Uso de weak self
        guard let self = self else { return "" } // Verificación de existencia de self
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

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

var paragraph: AnHTMLElement? = AnHTMLElement(name: "p", text: "hola, mundo")
print(paragraph!.asHTML())

paragraph = nil // AnHTMLElement se desinicializa

El uso de [unowned self] es posible si estás seguro de que la instancia de la clase (self) no será nil antes de que se ejecute el cierre.

ARC simplifica mucho la gestión de memoria en Swift en comparación con la gestión manual, pero requiere entender los tipos de referencias para evitar fugas de memoria.