Memory Graph: cos'è, grafo degli oggetti e rilevamento dei riferimenti ciclici

Autore: IT Sectr Pubblicato: 2026-05-07 Tempo di lettura: 10 min

Memory Graph è uno strumento visivo di Xcode Debug Navigator che mostra un grafo degli oggetti nella memoria dell'applicazione con i loro riferimenti reciproci. A differenza di un heap dump, Memory Graph mostra non solo un elenco di oggetti, ma un grafo orientato di riferimenti dove ogni nodo è un oggetto e ogni bordo è un riferimento (strong, weak, unowned). Secondo Apple WWDC 2018, lo strumento consente di rilevare visivamente retain cycles e perdite di memoria in pochi secondi, senza dover analizzare i dati grezzi dell'heap dump.

Punti chiave

  • Memory Graph è un grafo visivo degli oggetti nella memoria di Xcode, che mostra i riferimenti tra oggetti in tempo reale.
  • Retain cycle viene rilevato da un ciclo chiuso nel grafo — due o più oggetti si riferiscono l'un l'altro con riferimenti forti.
  • Backtrace per ogni bordo del grafo mostra dove e quando è stato stabilito il riferimento, semplificando la ricerca della fonte della perdita.
  • Filtraggio per nome della classe e tipo di riferimento (strong/weak) consente di isolare rapidamente gli oggetti problematici.
  • Integrazione con Memory Report in Xcode consente di tracciare le variazioni del consumo di memoria in tempo reale.

Cos'è Memory Graph e come funziona

Memory Graph è un componente di Xcode Debug Navigator (introdotto in Xcode 10, WWDC 2018) che costruisce un grafo orientato di tutti gli oggetti nella memoria del processo in debug. Ogni nodo del grafo è un'istanza di classe (Objective-C o Swift), ogni bordo è un riferimento a un altro oggetto. Il colore del bordo indica il tipo di riferimento: blu — strong, verde — weak, grigio — unowned. Il grafo è costruito sulla base dei dati di LLDB e dell'Objective-C runtime, quindi l'applicazione deve essere compilata in configurazione Debug con i simboli abilitati per un corretto funzionamento.

Come funziona: quando l'applicazione è in pausa su un breakpoint, Xcode richiede tramite LLDB al runtime tutti gli oggetti vivi e i loro riferimenti. LLDB utilizza objc_getClassList e itera attraverso le regioni di allocazione per costruire il grafo completo. Su ARM64 (Apple Silicon), vengono utilizzati ulteriori mezzi hardware per il tracciamento delle allocazioni senza rallentamenti. Il tempo di costruzione del grafo dipende dalla dimensione dell'heap: per un'applicazione iOS tipica (50–200 MB), il grafo viene costruito in 1–3 secondi.

Secondo Apple, Memory Graph è l'unico strumento in grado di visualizzare i retain cycles senza modifica del codice o aggiunta di strumentazione. A differenza di Instruments Leaks, Memory Graph funziona in tempo reale all'interno di Xcode e non richiede l'avvio separato del profiler. Questo lo rende il primo strumento di scelta per la diagnosi rapida delle perdite di memoria durante lo sviluppo.

Differenza tra Memory Graph e heap dump

Heap dump fornisce una tabella di tutti gli oggetti con numeri (shallow size, retained size) — ottimale per l'analisi quantitativa. Memory Graph fornisce un'immagine visiva delle connessioni — ottimale per trovare riferimenti ciclici. Gli strumenti si completano a vicenda: prima Memory Graph per il rilevamento rapido dei retain cycles, poi heap dump tramite Instruments Allocations per la misurazione precisa del retained size. Secondo objc.io, la combinazione di entrambi i metodi copre il 95% degli scenari di perdita di memoria.

Rilevamento dei retain cycles con Memory Graph

Retain cycle è una situazione in cui due o più oggetti si tengono reciprocamente con riferimenti forti, formando un ciclo chiuso. ARC non può deallocare tale ciclo perché il retain count di ogni oggetto non raggiunge mai zero. Un esempio classico: ViewController e View, dove View ha un riferimento forte a una closure che cattura self (ViewController). Memory Graph mostra tali cicli come anelli (cicli), evidenziandoli per una rapida identificazione.

Quando Xcode rileva un retain cycle, lo evidenzia con un contorno arancione e mostra un avviso in Debug Navigator. Cliccando sul ciclo, si vede la catena di riferimenti che formano il ciclo chiuso. Lo sviluppatore deve solo determinare quale bordo strong dovrebbe essere weak — di solito è un riferimento da un oggetto figlio al genitore (ad esempio, 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
    }
}

In Memory Graph vedrai un triangolo: ViewController → DataService → closure → ViewController. La soluzione è rendere la cattura di self debole: [weak self]. Dopo la correzione, Memory Graph mostrerà un bordo verde dalla closure a ViewController e il retain cycle scomparirà.

swift
// Codice corretto — cattura debole di self
service.fetchData { [weak self] data in
    guard let self else { return }
    self.updateUI(data)
}

Interfaccia di Memory Graph Debugger in Xcode

L'interfaccia di Memory Graph Debugger è composta da tre pannelli: sinistro — un elenco di tutti gli oggetti vivi (raggruppati per classe) con il conteggio delle istanze; centrale — un grafo visivo con nodi trascinabili; destro — un ispettore per l'oggetto o il bordo selezionato. L'elenco degli oggetti mostra: icona della classe, numero di istanze in memoria, retained size totale e percentuale dell'intero heap. Il filtraggio per nome della classe supporta le espressioni regolari.

Navigazione nel grafo

I nodi del grafo possono essere trascinati per migliorare la leggibilità. Un doppio clic su un nodo apre informazioni dettagliate sull'oggetto: tutte le sue proprietà con tipi e valori, lo stack di chiamate (backtrace) per ogni proprietà e la cronologia retain/release. Backtrace è una funzionalità chiave: mostra quale esatta riga di codice ha stabilito il riferimento all'oggetto. Questo consente di trovare la fonte della perdita senza rivedere manualmente tutto il codice.

Per grafi complessi, Xcode fornisce un layout automatico tramite Layout → Hierarchical o Cluster. Il layout gerarchico posiziona gli oggetti radice in alto e i figli in basso, semplificando la ricerca di catene. Il raggruppamento in cluster raggruppa oggetti correlati, utile quando il grafo contiene diversi gruppi isolati. Secondo Apple, per la maggior parte delle applicazioni si consiglia il layout gerarchico — è intuitivo e richiede meno tempo per l'analisi visiva.

lldb
// Comandi LLDB utilizzati internamente da Memory Graph
(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

Analisi del grafo: trovare e risolvere le perdite

Un approccio sistematico all'analisi di Memory Graph include diverse fasi. Fase 1: esegui l'applicazione, svolgi uno scenario che potrebbe causare una perdita (aprire/chiudere una schermata, effettuare una richiesta di rete). Fase 2: clicca sul pulsante Memory Graph in Debug Navigator — Xcode costruisce il grafo. Fase 3: verifica gli avvisi arancioni di retain cycles nel pannello sinistro. Fase 4: per oggetti sospetti, usa l'opzione Show only cycles — verranno mostrati solo i nodi coinvolti in riferimenti ciclici.

Uso del backtrace per trovare la fonte

Una volta trovato un retain cycle, clicca sul bordo del ciclo e apri il pannello dell'ispettore. La sezione Backtrace mostra lo stack di chiamate nel momento in cui questo riferimento è stato stabilito. Ad esempio, se il bordo porta da una closure a self, il backtrace mostrerà in quale metodo e in quale riga di codice la closure è stata creata. Questo elimina la necessità di indovinare — vedi immediatamente il punto in cui è stato creato il riferimento problematico. Secondo WWDC Labs, l'analisi del backtrace riduce il tempo di diagnosi del retain cycle da 15–20 minuti a 2–3 minuti.

swift
class ProfileViewController: UIViewController {
    var profileView: ProfileView!

    override func viewDidLoad() {
        super.viewDidLoad()
        profileView = ProfileView()
        // Memory Graph mostrerà il retain cycle qui
        profileView.onTap = { [unowned self] in
            // ⚠️ unowned può causare crash quando self è nil
            self.navigateToDetail()
        }
    }

    func navigateToDetail() { }
}

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

Filtraggio degli oggetti superflui

Memory Graph può mostrare migliaia di oggetti, rendendo difficile la ricerca. Usa i filtri nel pannello sinistro: inserisci un nome di classe (ad esempio, ProfileViewController) per mostrare solo le istanze di quella classe. Quindi seleziona un'istanza che avrebbe dovuto essere deallocata (se la schermata è chiusa ma l'oggetto rimane). Applica Show Reachable From — verranno mostrati solo i riferimenti pertinenti a questo oggetto, nascondendo il resto del grafo.

Consigli pratici per l'uso di Memory Graph

Gli sviluppatori esperti usano Memory Graph non solo per trovare perdite, ma anche per il controllo proattivo della memoria. Controlla Memory Graph dopo ogni grande cambiamento architetturale — aggiunta di un nuovo delegate, closure o abbonamento a NotificationCenter. Basta eseguire uno scenario tipico e assicurarsi che gli oggetti vengano deallocati correttamente e che non ci siano retain cycles. Questo richiede 2–3 minuti, ma previene ore di debug successivo.

Combinazione con Memory Report

Memory Report in Xcode (scheda Debug Navigator) mostra un grafico del consumo di memoria in tempo reale. Usalo insieme a Memory Graph: apri Memory Graph quando il consumo aumenta improvvisamente. Ad esempio, scorrendo una lunga lista con celle che caricano immagini, Memory Graph mostrerà quali oggetti vengono creati e quali vengono deallocati. Se il numero di oggetti cresce senza diminuire — è una potenziale perdita visibile prima che causi un crash. Secondo Apple, la combinazione Memory Graph + Memory Report è il flusso di lavoro consigliato per tutti gli sviluppatori iOS a partire da Xcode 12.

objective-c
// Esempio di perdita in Objective-C tramite delegation
@interface DownloadManager : NSObject
@property (strong) id delegate; // ❌ Deve essere weak!
@end

@implementation DownloadManager
// Memory Graph mostrerà il retain cycle:
// ViewController → DownloadManager.delegate → ViewController
@end

// Correzione: weak property
@property (weak) id delegate;

Profilazione delle closure

Presta particolare attenzione alle closure — la fonte più comune di retain cycles in Swift. Quando si cattura self all'interno di una closure memorizzata come proprietà di un oggetto, si forma un ciclo classico. Memory Graph lo mostra come una closure (un nodo con il simbolo {}) collegata da bordi blu agli oggetti catturati. Controlla regolarmente tutte le closure, specialmente quelle utilizzate in chiamate asincrone, GCD, Combine e SwiftUI. Secondo le statistiche di Point-Free, il 90% delle perdite nei progetti Swift è correlato a closure che catturano self.

Domande frequenti

Memory Graph funziona solo per Objective-C o anche per Swift?

Memory Graph funziona per entrambi i linguaggi perché utilizza l'Objective-C runtime. Gli oggetti Swift compatibili con ObjC (sottoclassi di NSObject marcate con @objc) vengono visualizzati completamente. Le strutture e le classi Swift pure senza ponte ObjC sono visibili con limitazioni.

Perché Memory Graph non mostra alcuni oggetti?

Gli oggetti devono essere registrati nell'Objective-C runtime. I tipi valore Swift (struct, enum) non vengono visualizzati. Assicurati che la classe erediti da NSObject o utilizzi l'attributo @objc per la visibilità in Memory Graph.

Come interpretare i colori dei bordi nel grafo?

Blu — riferimento strong, trattiene l'oggetto. Verde — riferimento weak, non influisce sul ciclo di vita. Grigio — riferimento unowned. Un retain cycle si forma solo da bordi blu.

Memory Graph rallenta l'applicazione?

La costruzione del grafo mette in pausa l'applicazione per 1–3 secondi e può aumentare temporaneamente il consumo di memoria di Xcode di 200–500 MB. L'applicazione stessa non viene rallentata poiché l'ispezione avviene durante la pausa del breakpoint.

È possibile esportare Memory Graph per l'analisi?

Xcode non supporta l'esportazione diretta del grafo. Usa uno screenshot per la documentazione o lo script lldb heap.find_variable per l'estrazione programmatica dei dati. Per un'analisi dettagliata, usa Instruments Allocations con un heap dump.

Riepilogo

  • Memory Graph è uno strumento visivo di Xcode per mostrare un grafo di oggetti in memoria con i loro riferimenti.
  • Retain cycle viene mostrato come un ciclo chiuso di bordi blu (strong) — Xcode lo evidenzia in arancione.
  • Backtrace per ogni bordo del grafo mostra la posizione esatta nel codice dove è stato creato il riferimento problematico.
  • Filtraggio per classi e tipi di riferimento consente di isolare le perdite in un grafo con migliaia di oggetti.
  • Closure sono la principale fonte di retain cycles in Swift, Memory Graph le mostra come nodi {}.
  • Weak e unowned sono soluzioni per spezzare il ciclo, ma weak è preferibile per la sicurezza quando è nil.
  • Controlli regolari di Memory Graph dopo modifiche all'architettura prevengono la regressione della memoria nel progetto.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche