Swift Conteo Automático de Referencias (ARC)

Swift usa el Conteo Automático de Referencias (ARC) como mecanismo para rastrear y administrar la memoria de la aplicación.

Normalmente no necesitamos liberar memoria manualmente, porque ARC libera automáticamente la memoria ocupada por la instancia de la clase cuando ya no se utiliza.

Pero en algunas ocasiones todavía necesitamos implementar la gestión de memoria en el código.

Funcionalidad de ARC

  • Cada vez que se usa el método init() para crear una nueva instancia de una clase, ARC asigna un gran bloque de memoria para almacenar la información de la instancia.

  • La memoria incluye información sobre el tipo de la instancia, así como los valores de todas las propiedades relacionadas con ella.

  • Cuando la instancia ya no se utiliza, ARC libera la memoria ocupada por la instancia y permite que esa memoria se destine a otros fines.

  • Para garantizar que las instancias en uso no sean destruidas, ARC rastrea y calcula cuántas propiedades, constantes y variables están referenciando a cada instancia.

  • Cuando una instancia se asigna a una propiedad, constante o variable, todas crean una referencia fuerte a esta instancia. Mientras la referencia fuerte siga existiendo, la instancia no puede ser destruida.

Ejemplo de ARC

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) 开始初始化")
    }
    deinit {
        print("\(name) 被析构")
    }
}

// 值会被自动初始化为nil,目前还不会引用到Person类的实例
var reference1: Person?
var reference2: Person?
var reference3: Person?

// 创建Person类的新实例
reference1 = Person(name: "Ejemplo")


//赋值给其他两个变量,该实例又会多出两个强引用
reference2 = reference1
reference3 = reference1

//断开第一个强引用
reference1 = nil
//断开第二个强引用
reference2 = nil
//断开第三个强引用,并调用析构函数
reference3 = nil

La salida del programa anterior es:

Ejemplo 开始初始化
Ejemplo 被析构

Ciclo de referencias fuertes entre instancias de clase

En el ejemplo anterior, ARC rastrea el número de referencias a la instancia de Person que acabas de crear, y destruye la instancia de Person cuando ya no se necesita.

Sin embargo, podemos escribir código en el que una clase nunca tenga 0 referencias fuertes. Esto sucede cuando dos instancias de clase mantienen una referencia fuerte entre sí e impiden que la otra sea destruida. Esto es lo que se llama un ciclo de referencias fuertes.

Ejemplo

El siguiente ejemplo muestra un caso en el que se produce accidentalmente un ciclo de referencias fuertes. El ejemplo define dos clases: Person y Apartment, para modelar un apartamento y sus residentes:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) 被析构") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { print("Apartment #\(number) 被析构") }
}

// 两个变量都被初始化为nil
var ejemplo: Person?
var number73: Apartment?

// 赋值
ejemplo = Person(name: "Ejemplo")
number73 = Apartment(number: 73)

// 意感叹号是用来展开和访问可选变量 ejemplo 和 number73 中的实例
// 循环强引用被创建
ejemplo!.apartment = number73
number73!.tenant = ejemplo

// 断开 ejemplo 和 number73 变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁
// 注意,当你把这两个变量设为nil时,没有任何一个析构函数被调用。
// 强引用循环阻止了Person和Apartment类实例的销毁,并在你的应用程序中造成了内存泄漏
ejemplo = nil
number73 = nil

Resolviendo ciclos de referencias fuertes entre instancias

Swift ofrece dos formas de resolver el problema de los ciclos de referencias fuertes que puedes encontrar al usar propiedades de clase:

  • Referencias débiles
  • Referencias no propietarias

Las referencias débiles y las referencias no propietarias permiten que una instancia dentro de un ciclo de referencias haga referencia a otra instancia sin mantener una referencia fuerte. De este modo, las instancias pueden referenciarse mutuamente sin crear un ciclo de referencias fuertes.

Usa referencias débiles para instancias que pueden volverse nil durante su ciclo de vida. Por el contrario, para instancias que, una vez asignadas en la inicialización, nunca vuelven a asignarse como nil, usa referencias no propietarias.

Ejemplo de referencia débil

class Module {
    let name: String
    init(name: String) { self.name = name }
    var sub: SubModule?
    deinit { print("\(name) 主模块") }
}

class SubModule {
    let number: Int
    
    init(number: Int) { self.number = number }
    
    weak var topic: Module?
    
    deinit { print("子模块 topic 数为 \(number)") }
}

var toc: Module?
var list: SubModule?
toc = Module(name: "ARC")
list = SubModule(number: 4)
toc!.sub = list
list!.topic = toc

toc = nil
list = nil

La salida del programa anterior es:

ARC 主模块
子模块 topic 数为 4

Ejemplo de referencia no propietaria

class Student {
    let name: String
    var section: Marks?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("\(name)") }
}
class Marks {
    let marks: Int
    unowned let stname: Student
    
    init(marks: Int, stname: Student) {
        self.marks = marks
        self.stname = stname
    }
    
    deinit { print("学生的分数为 \(marks)") }
}

var module: Student?
module = Student(name: "ARC")
module!.section = Marks(marks: 98, stname: module!)
module = nil

La salida del programa anterior es:

ARC
学生的分数为 98

Ciclos de referencias fuertes causados por cierres

El ciclo de referencias fuertes también ocurre cuando asignas un cierre a una propiedad de una instancia de clase, y el cuerpo del cierre utiliza esa instancia. El cuerpo del cierre puede acceder a alguna propiedad de la instancia, como self.someProperty, o llamar a algún método de la instancia, como self.someMethod. Ambos casos hacen que el cierre "capture" a self, lo que produce un ciclo de referencias fuertes.

Ejemplo

El siguiente ejemplo te muestra cómo se produce un ciclo de referencias fuertes cuando un cierre hace referencia a self. El ejemplo define una clase llamada HTMLElement, que usa un modelo simple para representar un elemento individual en HTML:

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        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) is being deinitialized")
    }
    
}

// 创建实例并打印信息
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

La clase HTMLElement genera un ciclo de referencias fuertes entre la instancia de clase y el cierre del valor predeterminado de asHTML.

La propiedad asHTML de la instancia mantiene una referencia fuerte al cierre. Sin embargo, el cierre usa self dentro de su cuerpo (refiriéndose a self.name y self.text), por lo que el cierre captura a self. Esto significa que el cierre, a su vez, mantiene una referencia fuerte a la instancia de HTMLElement. Así, los dos objetos generan un ciclo de referencias fuertes.

Resolviendo el ciclo de referencias fuertes causado por cierres: puedes resolver el ciclo de referencias fuertes entre un cierre y una instancia de clase definiendo una lista de capturas como parte del cierre al definirlo.


Referencias débiles y referencias no propietarias

Cuando el cierre y la instancia capturada siempre se referencian mutuamente y siempre se destruyen al mismo tiempo, define la captura dentro del cierre como una referencia no propietaria.

Por el contrario, cuando la referencia capturada a veces puede ser nil, define la captura dentro del cierre como una referencia débil.

Si la referencia capturada nunca se asigna a nil, se debe usar una referencia no propietaria en lugar de una referencia débil.

Ejemplo

En el ejemplo de HTMLElement anterior, la referencia no propietaria es la forma correcta de resolver el ciclo de referencias fuertes. Así se escribe la clase HTMLElement para evitar el ciclo de referencias fuertes:

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        [unowned self] in
        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) 被析构")
    }
    
}

//创建并打印HTMLElement实例
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

// HTMLElement实例将会被销毁,并能看到它的析构函数打印出的消息
paragraph = nil

La salida del programa anterior es:

<p>hello, world</p>
p 被析构
Otras extensiones