Automatic Reference Counting (ARC) es un sistema de gestión de memoria en Swift y Objective-C que cuenta automáticamente el número de referencias a cada objeto y lo libera cuando el contador llega a cero. Según la Documentación de Apple Swift, 2026, ARC está integrado en el compilador y funciona en tiempo de compilación, insertando llamadas retain/release en los lugares adecuados. A diferencia de Garbage Collection, ARC no requiere un hilo de recolección separado y no crea pausas durante la ejecución de la aplicación.
Puntos clave
ARC (Automatic Reference Counting) es un mecanismo de gestión de memoria basado en compilador introducido por Apple en Xcode 4.2 (2011) para Objective-C y heredado por Swift. A diferencia de la gestión manual de memoria (Manual Retain-Release, MRR), ARC automatiza completamente las llamadas retain, release y autorelease, insertándolas en tiempo de compilación sin intervención del desarrollador.
ARC no es un recolector de basura. Es análisis estático con inserción dinámica de código: el compilador analiza los tiempos de vida de los objetos y coloca retain/release en los puntos donde los objetos se crean, copian o salen del ámbito. El resultado es una liberación determinista de memoria: el objeto se elimina exactamente cuando ya no hay referencias apuntando a él, sin demoras ni pausas.
Según la WWDC 2011 Session 323, la transición de MRR a ARC redujo los errores relacionados con la memoria en un 70% en las aplicaciones de Apple. Los desarrolladores dejaron de equilibrar manualmente retain/release, eliminando toda una clase de fugas y errores de double-free.
Cada objeto en memoria tiene un contador de referencias (retain count). Cuando se crea un objeto, el contador se establece en 1. Cuando una nueva referencia strong apunta al objeto, el contador aumenta (retain). Cuando una referencia strong desaparece, el contador disminuye (release). Al llegar a cero, el objeto se libera inmediatamente.
El compilador de Swift inserta retain/release no en cada asignación: utiliza análisis estático para optimizar. Por ejemplo, si se garantiza que un objeto no se usará después de ser pasado, el compilador puede omitir un release/retain innecesario. Esta optimización se llama ARC Optimization.
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) initialized (retain count: 1)")
}
deinit {
print("\(name) deallocated")
}
}
func testARC() {
let p = Person(name: "Alice") // retain count = 1
let q = p // retain count = 2
// q sale del ámbito
// retain count = 1
// p sale del ámbito
// retain count = 0 → deinit
}
Este ejemplo muestra cómo ARC gestiona el contador: al asignar q = p, el contador aumenta; cuando q sale del ámbito, disminuye. Cuando desaparece la última referencia strong, el desinicializador se llama inmediatamente. Ningún recolector de basura espera — la memoria se libera al instante.
ARC y Garbage Collection resuelven el mismo problema — la gestión automática de memoria — pero con enfoques fundamentalmente diferentes. La elección entre ellos define la arquitectura del lenguaje: Swift (ARC) vs Java/Go (GC). Veamos las principales diferencias.
| Característica | ARC (Swift/ObjC) | GC (Java/Go) |
|---|---|---|
| Momento de liberación | Determinista: inmediatamente al llegar el contador a cero | No determinista: en el próximo ciclo de recolección |
| Pausas de ejecución | Ninguna (retain/release insertados en compilación) | Pausas Stop-The-World (2–200 ms) |
| Sobrecarga | Incremento/decremento del contador en cada referencia | Recorrido del grafo de objetos, marcado, liberación |
| Problemas | Retain Cycle (resolución manual) | Fragmentación del heap, fugas por referencias olvidadas |
| Hilo adicional | No requiere | Requiere hilo de recolección de basura |
El compromiso clave: ARC proporciona tiempos de vida predecibles y cero pausas, pero requiere que el desarrollador entienda los retain cycles y elija correctamente weak/unowned. GC libera al desarrollador de estas preocupaciones, pero a costa de pausas no deterministas y un hilo adicional.
ARC define tres tipos de calificadores de referencia, cada uno afecta el contador y el ciclo de vida del objeto de manera diferente. Elegir el calificador correcto es la base de la gestión segura de memoria en Swift.
Strong es el calificador por defecto. Cada referencia strong aumenta el retain count del objeto en 1. Mientras exista al menos una referencia strong, el objeto permanece vivo. Todas las propiedades de clase y variables locales en Swift son strong por defecto. Las referencias strong crean una relación de propiedad: el objeto A posee al objeto B.
Weak es una referencia que no aumenta el retain count. Un objeto puede ser liberado incluso si una referencia weak apunta a él. Después de la liberación, la referencia weak se establece automáticamente a nil. Las referencias weak siempre se declaran como var con tipo opcional (?). Se usan para romper retain cycles, especialmente en el patrón delegate.
Unowned es una referencia no propietaria que, como weak, no aumenta el retain count. Sin embargo, una referencia unowned no se establece a nil después de la liberación — acceder a un objeto liberado causa un crash. Unowned se usa cuando se garantiza que el objeto vive al menos tanto como el objeto referenciante. Los casos típicos son closures y relaciones padre-hijo con tiempo de vida garantizado.
class Customer {
let name: String
var card: CreditCard? // strong
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
class CreditCard {
let number: String
unowned let customer: Customer // unowned — no posee
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card \(number) deallocated") }
}
var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// Customer y CreditCard ambos liberados — sin retain cycle
Aquí CreditCard usa una referencia unowned a Customer. Customer posee la tarjeta (strong), y la tarjeta no posee al cliente (unowned). Cuando Customer se libera, ambos objetos se liberan — no se produce retain cycle. Si card.customer fuera strong, el ciclo bloquearía la liberación.
A pesar de la automatización, ARC no es una panacea. Los desarrolladores se encuentran con varios problemas típicos que requieren comprensión del mecanismo interno de gestión de memoria.
Las closures en Swift capturan variables externas por referencia strong. Si una closure se asigna a una propiedad de clase y captura self — se produce un retain cycle: la clase retiene la closure, la closure retiene self. La solución es una lista de captura con weak o unowned.
class NetworkManager {
var completionHandler: ((Data?) -> Void)?
var data: Data?
func fetchData() {
completionHandler = { [weak self] result in
guard let self else { return }
self.data = result
self.processResult()
}
}
func processResult() { }
}
La lista de captura [weak self] crea una referencia weak a self dentro de la closure. Esto rompe el potencial retain cycle. Guard let self garantiza que el objeto está vivo antes de ejecutar el código. weak self es la práctica estándar para closures asíncronas en Swift.
Aunque retain/release son operaciones ligeras, en bucles intensivos los incrementos/decrementos frecuentes del contador añaden sobrecarga. En Swift 5.9+, el compilador utiliza optimización que elimina retain/release redundantes si el analizador demuestra que es seguro. Sin embargo, en Objective-C, retain/release todavía pueden ser un cuello de botella en escenarios de alta carga con millones de llamadas por segundo.
Autorelease Pool es un mecanismo de liberación diferida utilizado en Objective-C y algunos escenarios de Swift. Los objetos se colocan en el pool y reciben release cuando el pool se vacía. En bucles con muchos objetos temporales (por ejemplo, análisis JSON), crear un autoreleasepool personalizado reduce el consumo máximo de memoria.
Preguntas frecuentes
En la gestión manual (MRR), el desarrollador llamaba explícitamente a retain, release y autorelease. ARC inserta estas llamadas automáticamente en tiempo de compilación, eliminando el riesgo de double-free, fugas por release olvidado y errores de equilibrio retain/release.
ARC gestiona solo objetos Objective-C y clases Swift. Para estructuras y punteros C/C++, ARC no se aplica — estos objetos se gestionan manualmente o mediante smart pointers de C++ (shared_ptr, unique_ptr). Los objetos Core Foundation (CFString, CGColor) tampoco están cubiertos por ARC.
weak — cuando el objeto puede liberarse antes que el objeto referenciante (delegados, closures asíncronas). unowned — cuando se garantiza que el objeto vive al menos tanto como el referenciante (padre-hijo donde el hijo no puede existir sin el padre). Si no estás seguro, elige weak.
Los tipos existenciales (protocol como tipo) en Swift envuelven el valor en un contenedor especial (contenedor existencial). Esto aumenta el número de retain/release en los límites de los protocolos. En Swift 5.7+, los tipos de resultado opaco y los parámetros some reducen la sobrecarga al eliminar el contenedor.
No hay una API directa para leer el retain count en Swift — se considera un detalle de implementación. Para diagnóstico, usa Instruments (Allocations, Leaks) o el Depurador de Memoria en Xcode. Estas herramientas muestran el número de instancias de clase vivas y las cadenas de retención.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también