Sobes.tech
Middle

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

sobes.tech AI

Respuesta de la IA

ARC (Contador de Referencias Automático) rastrea y gestiona automáticamente las referencias a instancias de clases. Libera la memoria ocupada por una instancia cuando ya no hay referencias fuertes a ella. ARC solo funciona con instancias de clases, no con estructuras o enumeraciones, ya que son tipos de valor.

Tres tipos de enlaces entre instancias:

  • Strong (Fuerte): Un enlace que incrementa el contador de referencias de la instancia. Por defecto, todas las referencias son fuertes.
  • Weak (Débil): Un enlace que no incrementa el contador de referencias. Se usa para prevenir ciclos de referencias fuertes. Las referencias de tipo weak siempre son opcionales, ya que la instancia puede ser liberada en cualquier momento.
  • Unowned (Sin dueño): Un enlace que no incrementa el contador de referencias y se usa cuando se garantiza que la instancia relacionada existirá más tiempo que la actual. No es obligatorio, pero intentar acceder a una instancia liberada provocará un error en tiempo de ejecución.

Los ciclos de referencias fuertes ocurren cuando dos o más instancias de clases tienen referencias fuertes entre sí, impidiendo su liberación por ARC. Para resolver estos ciclos, se usan referencias weak o unowned.

Ejemplo de ciclo de referencias fuertes:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
    }

    deinit { // llamado al liberar
        print("\(name) está siendo desalojado")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?

    init(unit: String) {
        self.unit = unit
    }

    deinit { // llamado al liberar
        print("Apartamento \(unit) está siendo desalojado")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil
unit4A = nil // No se imprime nada, ya que hay un ciclo de referencias fuertes

Resolución del ciclo con weak:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
    }

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

class Apartment {
    let unit: String
    weak var tenant: Person? // Referencia débil a Person

    init(unit: String) {
        self.unit = unit
    }

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

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Libera a Person
unit4A = nil // Libera a Apartment

Resolución con unowned:

class Customer {
    let name: String
    var card: CreditCard?

    init(name: String) {
        self.name = name
    }

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

class CreditCard {
    let number: UInt64
    unowned let customer: Customer // Referencia sin dueño a Customer

    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }

    deinit {
        print("Tarjeta #\(number) está siendo desalojada")
    }
}

var john: Customer?

john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

john = nil // Libera a Customer y CreditCard

ARC en cierres: Los cierres también pueden crear ciclos de referencias fuertes, capturando instancias de clases. Para prevenir esto, se usan listas de captura ([weak self] o [unowned self]).

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

    lazy var asHTML: () -> String = { // lazy para poder usar self antes de la inicialización
        [unowned self] in // Lista de captura con "unowned 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 desalojado")
    }
}

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

paragraph = nil // Libera HTMLElement