Memory Graph: qué es, grafo de objetos y detección de referencias cíclicas

Autor: IT Sectr Publicado: 2026-05-07 Tiempo de lectura: 10 min

Memory Graph es una herramienta visual del Xcode Debug Navigator que muestra un grafo de los objetos en la memoria de la aplicación con sus referencias mutuas. A diferencia de un heap dump, Memory Graph no solo muestra una lista de objetos, sino un grafo dirigido de referencias donde cada nodo es un objeto y cada arista es una referencia (strong, weak, unowned). Según Apple WWDC 2018, la herramienta permite detectar visualmente retain cycles y fugas de memoria en segundos, sin necesidad de analizar datos brutos del heap dump.

Puntos clave

  • Memory Graph es un grafo visual de objetos en la memoria de Xcode que muestra las referencias entre objetos en tiempo real.
  • Retain cycle se detecta por un bucle cerrado en el grafo: dos o más objetos se referencian mutuamente con referencias fuertes.
  • Backtrace de cada arista del grafo muestra dónde y cuándo se estableció la referencia, simplificando la búsqueda del origen de la fuga.
  • Filtrado por nombre de clase y tipo de referencia (strong/weak) permite aislar rápidamente los objetos problemáticos.
  • Integración con Memory Report en Xcode permite rastrear los cambios en el consumo de memoria en tiempo real.

Qué es Memory Graph y cómo funciona

Memory Graph es un componente del Xcode Debug Navigator (introducido en Xcode 10, WWDC 2018) que construye un grafo dirigido de todos los objetos en la memoria del proceso depurado. Cada nodo del grafo es una instancia de clase (Objective-C o Swift), cada arista es una referencia a otro objeto. El color de la arista indica el tipo de referencia: azul — strong, verde — weak, gris — unowned. El grafo se construye a partir de datos de LLDB y Objective-C runtime, por lo que la aplicación debe compilarse en configuración Debug con símbolos habilitados para un funcionamiento correcto.

Cómo funciona: cuando la aplicación se detiene en un breakpoint, Xcode solicita a través de LLDB todos los objetos vivos y sus referencias al runtime. LLDB utiliza objc_getClassList e itera sobre las regiones de asignación para construir el grafo completo. En ARM64 (Apple Silicon), se utilizan medios hardware adicionales para el seguimiento de asignaciones sin ralentización. El tiempo de construcción del grafo depende del tamaño del heap: para una aplicación iOS típica (50–200 MB), el grafo se construye en 1–3 segundos.

Según Apple, Memory Graph es la única herramienta que puede visualizar retain cycles sin modificación de código ni añadido de instrumentación. A diferencia de Instruments Leaks, Memory Graph funciona en tiempo real dentro de Xcode y no requiere un inicio separado del perfilador. Esto lo convierte en la primera herramienta de elección para la diagnóstica rápida de fugas de memoria durante el desarrollo.

En qué se diferencia Memory Graph de un heap dump

Heap dump proporciona una tabla de todos los objetos con números (shallow size, retained size) — es óptimo para el análisis cuantitativo. Memory Graph proporciona una imagen visual de las conexiones — óptimo para encontrar referencias cíclicas. Las herramientas se complementan: primero Memory Graph para la detección rápida de retain cycles, luego heap dump mediante Instruments Allocations para la medición precisa del retained size. Según objc.io, la combinación de ambos métodos cubre el 95% de los escenarios de fugas de memoria.

Detección de retain cycles con Memory Graph

Retain cycle es una situación en la que dos o más objetos se mantienen entre sí con referencias fuertes, formando un bucle cerrado. ARC no puede desasignar dicho bucle porque el retain count de cada objeto nunca llega a cero. Un ejemplo clásico: ViewController y View, donde View tiene una referencia fuerte a un closure que captura self (ViewController). Memory Graph muestra estos bucles como anillos (ciclos), resaltándolos para una identificación rápida.

Cuando Xcode detecta un retain cycle, lo resalta con un contorno naranja y muestra una advertencia en el Debug Navigator. Al hacer clic en el ciclo, se muestra la cadena de referencias que forman el bucle cerrado. El desarrollador solo necesita determinar qué arista strong debería ser weak — normalmente es una referencia de un objeto hijo al padre (por ejemplo, delegate o closure).

swift
class ViewController: UIViewController {
    let service = DataService()

    override func viewDidLoad() {
        super.viewDidLoad()
        // ❌ Retain cycle: ViewController → service → closure → ViewController
        service.fetchData { self.updateUI($0) }
    }

    func updateUI(_ data: Data) {}
}

class DataService {
    var completion: ((Data) -> Void)?

    func fetchData(handler: @escaping (Data) -> Void) {
        self.completion = handler
    }
}

En Memory Graph verás un triángulo: ViewController → DataService → closure → ViewController. La solución es hacer que la captura de self sea débil: [weak self]. Después de la corrección, Memory Graph mostrará una arista verde desde el closure hasta ViewController, y el retain cycle desaparecerá.

swift
// Código corregido — captura débil de self
service.fetchData { [weak self] data in
    guard let self else { return }
    self.updateUI(data)
}

Interfaz del Memory Graph Debugger en Xcode

La interfaz del Memory Graph Debugger consta de tres paneles: el izquierdo — una lista de todos los objetos vivos (agrupados por clase) con el número de instancias; el central — un grafo visual con nodos arrastrables; el derecho — un inspector del objeto o arista seleccionados. La lista de objetos muestra: el icono de la clase, el número de instancias en memoria, el retained size total y el porcentaje de todo el heap. El filtrado por nombre de clase admite expresiones regulares.

Navegación por el grafo

Los nodos del grafo se pueden arrastrar para mejorar la legibilidad. Al hacer doble clic en un nodo, se abre información detallada del objeto: todas sus propiedades con tipos y valores, la pila de llamadas (backtrace) para cada propiedad y el historial de retain/release. Backtrace es una función clave: muestra qué línea de código exacta estableció la referencia al objeto. Esto permite encontrar el origen de la fuga sin revisar manualmente todo el código.

Para grafos complejos, Xcode proporciona diseño automático mediante Layout → Hierarchical o Cluster. El diseño jerárquico coloca los objetos raíz arriba y los hijos abajo, simplificando la búsqueda de cadenas. La agrupación en clústeres agrupa objetos relacionados, lo que es útil cuando el grafo contiene varios grupos aislados. Según Apple, para la mayoría de las aplicaciones se recomienda el diseño jerárquico: es intuitivo y requiere menos tiempo para el análisis visual.

lldb
// Comandos LLDB utilizados por Memory Graph internamente
(lldb) script import lldb.macosx.heap
(lldb) script heap.find_variable("viewController")
0x600000c4b80: ViewController
(lldb) script heap.refs 0x600000c4b80
0x600000c4b80 -> 0x600003a4c00 (DataService)
    ivar: _service, offset: 16

Análisis del grafo: cómo encontrar y solucionar fugas

Un enfoque sistemático para el análisis de Memory Graph incluye varias etapas. Etapa 1: ejecuta la aplicación, realiza un escenario que potencialmente cause una fuga (abrir/cerrar una pantalla, hacer una solicitud de red). Etapa 2: haz clic en el botón Memory Graph en Debug Navigator — Xcode construye el grafo. Etapa 3: verifica las advertencias naranjas de retain cycles en el panel izquierdo. Etapa 4: para objetos sospechosos, usa la opción Show only cycles — solo se mostrarán los nodos involucrados en referencias cíclicas.

Uso de backtrace para encontrar el origen

Una vez encontrado un retain cycle, haz clic en la arista del ciclo y abre el panel del inspector. La sección Backtrace muestra la pila de llamadas en el momento en que se estableció esta referencia. Por ejemplo, si la arista va de un closure a self, el backtrace mostrará en qué método y en qué línea de código se creó el closure. Esto elimina la necesidad de adivinar: ves inmediatamente el punto donde se creó la referencia problemática. Según WWDC Labs, el análisis de backtrace reduce el tiempo de diagnóstico de retain cycles de 15–20 minutos a 2–3 minutos.

swift
class ProfileViewController: UIViewController {
    var profileView: ProfileView!

    override func viewDidLoad() {
        super.viewDidLoad()
        profileView = ProfileView()
        // Memory Graph mostrará el retain cycle aquí
        profileView.onTap = { [unowned self] in
            // ⚠️ unowned puede causar crash cuando self es nil
            self.navigateToDetail()
        }
    }

    func navigateToDetail() { }
}

// ✅ Correcto: [weak self] + guard let self
profileView.onTap = { [weak self] in
    guard let self else { return }
    self.navigateToDetail()
}

Filtrado de objetos innecesarios

Memory Graph puede mostrar miles de objetos, dificultando la búsqueda. Usa filtros en el panel izquierdo: introduce un nombre de clase (por ejemplo, ProfileViewController) para mostrar solo las instancias de esa clase. Luego selecciona una instancia que debería haberse desasignado (si la pantalla está cerrada pero el objeto permanece). Aplica Show Reachable From — solo se mostrarán las referencias relevantes para este objeto, ocultando el resto del grafo.

Consejos prácticos para usar Memory Graph

Los desarrolladores experimentados usan Memory Graph no solo para encontrar fugas, sino también para el control proactivo de la memoria. Revisa Memory Graph después de cada cambio arquitectónico importante — añadir un nuevo delegate, closure o suscripción a NotificationCenter. Basta con ejecutar un escenario típico y asegurarse de que los objetos se desasignan correctamente y no hay retain cycles. Esto toma 2–3 minutos, pero previene horas de depuración posteriores.

Combinación con Memory Report

Memory Report en Xcode (pestaña Debug Navigator) muestra un gráfico del consumo de memoria en tiempo real. Úsalo junto con Memory Graph: abre Memory Graph cuando el consumo aumente bruscamente. Por ejemplo, al hacer scroll en una lista larga con celdas que cargan imágenes, Memory Graph mostrará qué objetos se crean y cuáles se desasignan. Si el número de objetos crece sin disminuir — es una fuga potencial visible antes de que provoque un crash. Según Apple, la combinación Memory Graph + Memory Report es el flujo de trabajo recomendado para todos los desarrolladores iOS a partir de Xcode 12.

objective-c
// Ejemplo de fuga en Objective-C mediante delegation
@interface DownloadManager : NSObject
@property (strong) id delegate; // ❌ ¡Debe ser weak!
@end

@implementation DownloadManager
// Memory Graph mostrará el retain cycle:
// ViewController → DownloadManager.delegate → ViewController
@end

// Solución: weak property
@property (weak) id delegate;

Perfilado de closures

Presta especial atención a los closures — la fuente más común de retain cycles en Swift. Al capturar self dentro de un closure que se almacena como propiedad de un objeto, se forma un ciclo clásico. Memory Graph lo muestra como un closure (un nodo con el símbolo {}) conectado con aristas azules a los objetos capturados. Revisa regularmente todos los closures, especialmente los utilizados en llamadas asíncronas, GCD, Combine y SwiftUI. Según las estadísticas de Point-Free, el 90% de las fugas en proyectos Swift están relacionadas con closures que capturan self.

Preguntas frecuentes

¿Memory Graph funciona solo para Objective-C o también para Swift?

Memory Graph funciona para ambos idiomas porque utiliza el Objective-C runtime. Los objetos Swift compatibles con ObjC (subclases de NSObject marcadas con @objc) se muestran completamente. Las estructuras y clases Swift puras sin puente ObjC se ven con limitaciones.

¿Por qué Memory Graph no muestra algunos objetos?

Los objetos deben estar registrados en el Objective-C runtime. Los Swift value types (struct, enum) no se muestran. Asegúrate de que la clase herede de NSObject o use el atributo @objc para ser visible en Memory Graph.

¿Cómo interpretar los colores de las aristas en el grafo?

Azul — referencia strong, retiene el objeto. Verde — referencia weak, no afecta al ciclo de vida. Gris — referencia unowned. Un retain cycle se forma solo con aristas azules.

¿Memory Graph ralentiza la aplicación?

La construcción del grafo pausa la aplicación durante 1–3 segundos y puede aumentar temporalmente el consumo de memoria de Xcode en 200–500 MB. La aplicación en sí no se ralentiza porque la inspección ocurre durante la pausa del breakpoint.

¿Se puede exportar Memory Graph para análisis?

Xcode no admite la exportación directa del grafo. Usa una captura de pantalla para documentación o el script de lldb heap.find_variable para extracción programática de datos. Para un análisis detallado, usa Instruments Allocations con un heap dump.

Resumen

  • Memory Graph es una herramienta visual de Xcode para mostrar un grafo de objetos en memoria con sus referencias.
  • Retain cycle se muestra como un bucle cerrado de aristas azules (strong) — Xcode lo resalta en naranja.
  • Backtrace de cada arista del grafo muestra la ubicación exacta en el código donde se creó la referencia problemática.
  • Filtrado por clases y tipos de referencia permite aislar fugas en un grafo con miles de objetos.
  • Closures son la fuente principal de retain cycles en Swift, Memory Graph los muestra como nodos {}.
  • Weak y unowned son soluciones para romper el ciclo, pero weak es preferible por seguridad cuando es nil.
  • Revisiones periódicas de Memory Graph después de cambios arquitectónicos previenen la regresión de memoria en el proyecto.

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.

Discutir el proyecto

Lea también