He pasado demasiadas noches arreglando cierres inesperados (crashes) por OOM (Out of Memory) en apps con millones de usuarios. Si tu aplicación se vuelve lenta tras 15 minutos de uso o el sistema operativo la mata silenciosamente en segundo plano, tienes una fuga de memoria. Aquí no vamos a adivinar; vamos a usar Xcode Instruments y el patrón Allocations Mark Generation para aislar el problema con precisión quirúrgica.
1. El Diagnóstico Rápido: Debug Memory Graph
Antes de abrir Instruments, usa la herramienta integrada en Xcode. Es tu primera línea de defensa para encontrar Swift Retain Cycles obvios.
Ejecuta tu app y navega a la pantalla sospechosa. Luego, presiona el botón de Debug Memory Graph (los tres nodos conectados) en la barra de depuración.
Busca en el navegador izquierdo:
- Signos de exclamación morados: Xcode ha detectado una fuga automática.
- Instancias duplicadas: Si ves
ProductViewController (5)y solo debería haber uno en pantalla, tienes controladores "zombies" que no se están liberando.
2. Análisis Profundo: Instruments Allocations
La herramienta "Leaks" de Instruments es útil, pero ingenua. A menudo no detecta ciclos de retención complejos donde la memoria técnicamente "sigue referenciada" pero es inaccesible para el usuario. Para Depuración de Apps seria, usamos Allocations con la técnica de Generaciones.
El Protocolo "Mark Generation"
Este es el flujo de trabajo exacto que usamos para auditar el Rendimiento iOS:
1. Abre Instruments (`Cmd + I`) y selecciona Allocations. 2. Presiona Grabar (Record). 3. Espera a que la app se estabilice en el menú principal. 4. En el panel derecho "Display Settings", presiona el botón "Mark Generation". Aparecerá una bandera roja. 5. Acción: Entra a la pantalla sospechosa (Push ViewController). 6. Regresión: Sal de la pantalla (Pop ViewController). 7. Presiona "Mark Generation" nuevamente. 8. Repite los pasos 5-7 tres veces.3. La Solución: Rompiendo Retain Cycles en Swift
El 90% de las Fugas de memoria iOS en Swift ocurren en closures (cierres) que capturan fuertemente a `self`. El compilador no siempre te avisará.
Analicemos un patrón común que causa fugas silenciosas:
// ❌ BAD: Strong Reference Cycle
class ProfileViewController: UIViewController {
let viewModel = ProfileViewModel()
override func viewDidLoad() {
super.viewDidLoad()
// El closure captura 'self' fuertemente.
// Si viewModel es propiedad de self, se crean referencias mutuas.
viewModel.onDataUpdated = { data in
self.updateUI(data)
}
}
}
Para corregir esto, debemos usar [weak self]. Esto convierte la captura en una referencia débil, permitiendo que el ARC (Automatic Reference Counting) libere la memoria cuando el controlador se cierra.
// ✅ GOOD: Weak Capture List
class ProfileViewController: UIViewController {
let viewModel = ProfileViewModel()
override func viewDidLoad() {
super.viewDidLoad()
// [weak self] evita el ciclo de retención
viewModel.onDataUpdated = { [weak self] data in
// Boilerplate seguro para desenpaquetar self
guard let self = self else { return }
self.updateUI(data)
}
}
}
weak. unowned asumirá que el objeto siempre existe y causará un crash inmediato si se accede a memoria liberada, lo cual es peor que una pequeña fuga temporal.
Delegados y Combine
No olvides revisar tus protocolos. Un delegado siempre debe ser weak para evitar retener al padre.
protocol ProfileDelegate: AnyObject { // 'AnyObject' es obligatorio para usar weak
func didUpdateProfile()
}
class ProfileView {
weak var delegate: ProfileDelegate? // Sin 'weak', esto es una fuga garantizada
}
| Herramienta | Caso de Uso Ideal | Precisión |
|---|---|---|
| Xcode Memory Graph | Inspección visual rápida en desarrollo | Media (Muestra relaciones visuales) |
| Instruments Leaks | Detección de memoria no referenciada (malloc) | Alta (Pero alcance limitado) |
| Instruments Allocations | Análisis de crecimiento de memoria y Zombies | Muy Alta (Estándar de Oro) |
Conclusión
Las fugas de memoria no se resuelven reiniciando el simulador. Requieren disciplina en el uso de [weak self] y auditorías regulares con Xcode Instruments. Si implementas el ciclo de "Mark Generation" en tu proceso de QA antes de cada lanzamiento, eliminarás los OOM crashes y garantizarás una experiencia fluida para tus usuarios.
Post a Comment