¿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:
- Referencias Fuertes (Strong References): Aumentan el contador de referencias a la instancia.
- 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
nilal desinicializarse el objeto al que apunta. - 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
nilantes 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.