Historia de la cuestión:
Con el desarrollo de Objective-C y la aparición de Swift, Apple se planteó la cuestión de la gestión automática y segura de la memoria de las aplicaciones sin que el programador tuviera que trabajar explícitamente con los comandos retain/release. Swift utiliza Automatic Reference Counting (ARC), que permite rastrear el número de referencias a los objetos-clase y liberar automáticamente la memoria cuando el último propietario desaparece.
Problema:
Dado que Swift es un lenguaje multiparadigma, opera tanto con tipos de valor (struct, enum) como con tipos de referencia (class). Para los últimos, es importante que la memoria se libere a tiempo; de lo contrario, puede haber una fuga de memoria. Una situación complicada es el retain cycle (enlaces cíclicos), cuando dos objetos se referencian entre sí, impidiendo que ARC libere la memoria.
Solución:
Swift aplica ARC solo a los objetos de clases. Cuando el número de referencias llega a 0, se libera la memoria. Para resolver el problema del retain cycle, se utilizan las palabras clave weak/unowned.
Ejemplo de código:
class Person { var name: String var pet: Pet? init(name: String) { self.name = name } deinit { print("\(name) está siendo desinicializado") } } class Pet { var owner: Person? init() {} deinit { print("Pet está siendo desinicializado") } } var tom: Person? = Person(name: "Tom") var cat: Pet? = Pet() tom!.pet = cat cat!.owner = tom tom = nil cat = nil // Ambos objetos NO se liberan, se produce un retain cycle
Características clave:
¿Se puede aplicar ARC a tipos de valor?
No, ARC solo rastrea referencias a objetos de clases. Las estructuras y enum se pasan por valor, y Swift libera su memoria automáticamente (generalmente al salir del ámbito).
¿Qué pasará si no se utiliza weak/unowned en las propiedades de dos objetos que se refieren mutuamente?
Se producirá un enlace cíclico (retain cycle), lo que significa que ARC nunca liberará la memoria de estos objetos. Tal código provocará una fuga de memoria; siempre use weak (o unowned) para al menos uno de los lados de tal enlace.
¿Para qué sirve unowned si ya hay weak?
unowned se necesita en casos donde la referencia siempre debe existir durante la vida de otro objeto y nunca debe volverse nil. weak es una referencia nula, unowned es no nula, pero no incrementa el contador de retenciones.
Ejemplo de código:
class A { var b: B? } class B { unowned var a: A // nunca es nil durante la vida de B }
Dos controladores tienen referencias fuertes entre sí: uno a través de una propiedad de delegado, el segundo a través de un arreglo de controladores hijos. El creador no usa weak en el delegado.
Pros: Los errores no son evidentes de inmediato, todo "funciona" a primera vista.
Contras: Con el crecimiento de la aplicación, la memoria no se libera y aparecen fugas; al trabajar con un volumen limitado de memoria, pueden ocurrir bloqueos.
El delegado y el observador de eventos se configuran como weak, el arreglo de controladores se limpia a medida que los controladores desaparecen. Todo está cuidadosamente documentado.
Pros: No hay fugas, todos los objetos se liberan a tiempo, no hay pérdida de rendimiento.
Contras: El delegado puede perderse inesperadamente (si se olvida de una referencia fuerte en algún lugar por encima); requiere atención al diseñar la arquitectura de la aplicación.