Memory Graph: ce este, graful obiectelor și detectarea referințelor ciclice

Autor: IT Sectr Publicat: 2026-05-07 Timp de citire: 10 min

Memory Graph — instrumentul vizual al Xcode Debug Navigator care afișează graful obiectelor din memoria RAM a aplicației cu referințele lor reciproce. Spre deosebire de heap dump, Memory Graph arată nu doar o listă de obiecte, ci un graf direcționat de referințe, unde fiecare nod este un obiect, iar fiecare muchie este o referință (strong, weak, unowned). Conform Apple WWDC 2018, instrumentul permite detectarea vizuală a retain cycles și a scurgerilor de memorie în câteva secunde, fără a fi nevoie de analiza datelor brute ale heap dump.

Principalele puncte

  • Memory Graph — graful vizual al obiectelor din memoria Xcode, care arată referințele dintre obiecte în timp real.
  • Retain cycle este detectat printr-un contur închis în graf — două sau mai multe obiecte se referă reciproc prin referințe puternice.
  • Backtrace pentru fiecare muchie a grafului arată unde și când a fost stabilită referința, simplificând găsirea sursei scurgerii.
  • Filtrarea după numele clasei și tipul referinței (strong/weak) permite izolarea rapidă a obiectelor problematice.
  • Integrarea cu Memory Report în Xcode permite urmărirea modificării consumului de memorie în timp real.

Ce este Memory Graph și cum funcționează

Memory Graph — este o componentă a Xcode Debug Navigator (a apărut în Xcode 10, WWDC 2018) care construiește un graf direcționat al tuturor obiectelor din memoria procesului depanat. Fiecare nod al grafului este o instanță de clasă (Objective-C sau Swift), fiecare muchie este o referință către un alt obiect. Culoarea muchiei indică tipul referinței: albastre — strong, verzi — weak, gri — unowned. Graful se construiește pe baza datelor LLDB și Objective-C runtime, de aceea pentru o funcționare corectă aplicația trebuie compilată în configurația Debug cu simbolurile activate.

Principiul de funcționare: când aplicația este oprită la un breakpoint, Xcode prin LLDB solicită runtime-ului toate obiectele vii și referințele lor. LLDB folosește objc_getClassList și iterarea prin regiunile de alocare pentru construirea grafului complet. Pe ARM64 (Apple Silicon) se utilizează suplimentar mijloace hardware pentru urmărirea alocărilor fără încetinire. Timpul de construire a grafului depinde de dimensiunea heap-ului: pentru o aplicație iOS tipică (50–200 MB) graful se construiește în 1–3 secunde.

Conform Apple, Memory Graph este singurul instrument care poate vizualiza retain cycles fără modificarea codului sau adăugarea de instrumentație. Spre deosebire de Instruments Leaks, Memory Graph funcționează în timp real în interiorul Xcode și nu necesită lansarea separată a profilatorului. Acest lucru îl face instrumentul de primă alegere pentru diagnosticarea rapidă a scurgerilor de memorie în procesul de dezvoltare.

Cu ce diferă Memory Graph de heap dump

Heap dump oferă un tabel al tuturor obiectelor cu numere (shallow size, retained size) — este optim pentru analiza cantitativă. Memory Graph oferă o imagine vizuală a conexiunilor — este optim pentru căutarea referințelor ciclice. Instrumentele se completează reciproc: mai întâi Memory Graph pentru detectarea rapidă a retain cycles, apoi heap dump prin Instruments Allocations pentru măsurarea precisă a retained size. Conform experienței objc.io, combinația celor două metode acoperă 95% din scenariile de scurgeri de memorie.

Detectarea retain cycles cu Memory Graph

Retain cycle — situația în care două sau mai multe obiecte se mențin reciproc prin referințe puternice, formând un contur închis. ARC nu poate elibera un astfel de contur, deoarece retain count al fiecărui obiect nu ajunge niciodată la zero. Exemplul clasic: ViewController și View, unde View are o strong reference către un closure care capturează self (ViewController). Memory Graph afișează astfel de conturi sub formă de inele (cicluri), evidențiindu-le pentru identificare rapidă.

Când Xcode detectează un retain cycle, îl evidențiază cu un contur portocaliu și arată o avertizare în Debug Navigator. Făcând clic pe ciclu, vedeți lanțul de referințe care formează conturul închis. Dezvoltatorului îi rămâne să determine care dintre muchiile strong trebuie să fie weak — de obicei aceasta este referința de la obiectul copil către părinte (de exemplu, delegate sau closure).

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

    override func viewDidLoad() {
        super.viewDidLoad()
        // ❌ Ciclu retain: ViewController → serviciu → 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
    }
}

În Memory Graph veți vedea un triunghi: ViewController → DataService → closure → ViewController. Soluția — capturarea slabă a lui self: [weak self]. După corectare, Memory Graph va arăta o muchie verde de la closure la ViewController, iar retain cycle va dispărea.

swift
// Cod corectat — capturarea slabă a self
service.fetchData { [weak self] data in
    guard let self else { return }
    self.updateUI(data)
}

Interfața Memory Graph Debugger în Xcode

Interfața Memory Graph Debugger constă din trei panouri: stânga — lista tuturor obiectelor vii (grupate pe clase) cu numărul de instanțe; centru — graful vizual cu noduri care pot fi mutate prin tragere; dreapta — inspectorul obiectului sau muchiei selectate. În lista de obiecte se afișează: pictograma clasei, numărul de instanțe în memorie, retained size total și procentul din întregul heap. Filtrarea după numele clasei suportă expresii regulate.

Navigarea prin graf

Nodurile grafului pot fi mutate prin tragere pentru îmbunătățirea lizibilității. Dublu clic pe un nod deschide informații detaliate despre obiect: toate proprietățile sale cu tipuri și valori, stiva de apeluri (backtrace) pentru fiecare proprietate și istoricul retain/release. Backtrace — funcția cheie: arată care linie de cod exact a stabilit referința către obiect. Aceasta permite găsirea sursei scurgerii fără a parcurge manual întregul cod.

Pentru grafuri complexe, Xcode oferă un aranjament automat prin Layout → Hierarchical (ierarhic) sau Cluster (cluster). Aranjamentul ierarhic plasează obiectele rădăcină în partea de sus, cele copil — în partea de jos, simplificând căutarea lanțurilor. Aranjamentul cluster grupează obiectele înrudite în clustere, ceea ce este convenabil când graful conține mai multe grupuri izolate. Conform Apple, pentru majoritatea aplicațiilor se recomandă aranjamentul ierarhic — este intuitiv și necesită mai puțin timp pentru analiza vizuală.

lldb
// Comenzile LLDB utilizate de Memory Graph sub capotă
(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

Analiza grafului: găsirea și eliminarea scurgerilor

Abordarea sistematică a analizei Memory Graph include mai multe etape. Etapa 1: lansați aplicația, executați scenariul care cauzează potențial o scurgere (deschideți/închideți ecranul, efectuați o cerere de rețea). Etapa 2: apăsați butonul Memory Graph din Debug Navigator — Xcode va construi graful. Etapa 3: verificați avertismentele portocalii de retain cycles în panoul stâng. Etapa 4: pentru obiectele suspecte utilizați opțiunea Show only cycles — se vor afișa doar nodurile implicate în referințe ciclice.

Utilizarea backtrace pentru găsirea sursei

Când retain cycle a fost găsit, faceți clic pe muchia ciclului și deschideți panoul inspectorului. În secțiunea Backtrace este afișată stiva de apeluri în momentul în care această referință a fost stabilită. De exemplu, dacă muchia duce de la closure la self, backtrace va arăta în ce metodă și pe ce linie de cod a fost creat closure-ul. Aceasta elimină necesitatea de a ghici — vedeți imediat punctul de creare al referinței problematice. Conform WWDC Labs, analiza backtrace reduce timpul de diagnosticare a retain cycle de la 15–20 minute la 2–3 minute.

swift
class ProfileViewController: UIViewController {
    var profileView: ProfileView!

    override func viewDidLoad() {
        super.viewDidLoad()
        profileView = ProfileView()
        // Memory Graph va arăta retain cycle aici
        profileView.onTap = { [unowned self] in
            // ⚠️ unowned poate cauza crash la nil self
            self.navigateToDetail()
        }
    }

    func navigateToDetail() { }
}

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

Filtrarea obiectelor inutile

Memory Graph poate afișa mii de obiecte, îngreunând căutarea. Utilizați filtrele în panoul stâng: introduceți numele clasei (de exemplu, ProfileViewController) pentru a afișa doar instanțele acelei clase. Apoi selectați instanța care ar fi trebuit să fie eliberată (dacă ecranul este închis, dar obiectul a rămas). Aplicați Show Reachable From — se vor afișa doar referințele relevante pentru acest obiect, ascunzând restul grafului.

Sfaturi practice pentru utilizarea Memory Graph

Dezvoltatorii experimentați folosesc Memory Graph nu doar pentru căutarea scurgerilor, ci și pentru controlul proactiv al memoriei. Verificați Memory Graph după fiecare modificare majoră de arhitectură — adăugarea unui nou delegate, closure sau abonament la NotificationCenter. Este suficient să executați un scenariu tipic și să vă asigurați că obiectele se eliberează corect, iar retain cycles lipsesc. Aceasta durează 2–3 minute, dar previne ore de depanare ulterioară.

Combinarea cu Memory Report

Memory Report în Xcode (filă Debug Navigator) arată graficul consumului de memorie în timp real. Utilizați-l împreună cu Memory Graph: deschideți Memory Graph la o creștere bruscă a consumului. De exemplu, la derularea unei liste lungi cu celule care încarcă imagini, Memory Graph va arăta ce obiecte se creează și care se eliberează. Dacă numărul de obiecte crește fără a scădea — aceasta este o potențială scurgere, vizibilă înainte de a duce la un crash. Conform Apple, combinația Memory Graph + Memory Report este workflow-ul recomandat pentru toți dezvoltatorii iOS începând cu Xcode 12.

objective-c
// Exemplu de scurgere în Objective-C prin delegare
@interface DownloadManager : NSObject
@property (strong) id delegate; // ❌ Trebuie să fie weak!
@end

@implementation DownloadManager
// Memory Graph va arăta retain cycle:
// ViewController → DownloadManager.delegate → ViewController
@end

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

Profilarea closure-urilor

Acordați o atenție deosebită closure-urilor — cea mai frecventă sursă de retain cycles în Swift. La capturarea lui self în interiorul unui closure care este stocat ca proprietate a obiectului, se formează un ciclu clasic. Memory Graph afișează aceasta ca un closure (nod cu simbolul {}), conectat prin muchii albastre cu obiectele capturate. Verificați regulat toate closure-urile, în special cele utilizate în apeluri asincrone, GCD, Combine și SwiftUI. Conform statisticilor Point-Free, 90% din scurgerile din proiectele Swift sunt legate de closure-uri care capturează self.

Întrebări frecvente

Memory Graph funcționează doar pentru Objective-C sau și pentru Swift?

Memory Graph funcționează pentru ambele limbaje, deoarece utilizează Objective-C runtime. Obiectele Swift compatibile cu ObjC (moștenitoare ale NSObject, marcate cu @objc) sunt afișate complet. Structurile și clasele Swift pure fără puntea ObjC sunt vizibile limitat.

De ce Memory Graph nu arată unele obiecte?

Obiectele trebuie să fie înregistrate în Objective-C runtime. Swift value types (struct, enum) nu sunt afișate. Asigurați-vă că clasa moștenește NSObject sau utilizează atributul @objc pentru vizibilitate în Memory Graph.

Cum se interpretează culorile muchiilor în graf?

Albastru — strong reference, menține obiectul. Verde — weak reference, nu afectează ciclul de viață. Gri — unowned reference. Retain cycle se formează doar din muchii albastre.

Încetinește Memory Graph aplicația?

Construirea grafului oprește aplicația pentru 1–3 secunde și poate crește temporar consumul de memorie al Xcode cu 200–500 MB. Aplicația în sine nu încetinește, deoarece inspecția are loc în timpul pauzei la breakpoint.

Se poate exporta Memory Graph pentru analiză?

Xcode nu suportă exportul direct al grafului. Utilizați o captură de ecran pentru documentație sau scriptul lldb heap.find_variable pentru extragerea programatică a datelor. Pentru analiză detaliată utilizați Instruments Allocations cu heap dump.

Concluzii

  • Memory Graph — instrumentul vizual Xcode pentru afișarea grafului obiectelor din memorie cu referințele lor.
  • Retain cycle este afișat ca un contur închis din muchii albastre (strong) — Xcode îl evidențiază cu portocaliu.
  • Backtrace pentru fiecare muchie a grafului arată locul exact din cod unde a fost creată referința problematică.
  • Filtrarea după clase și tipul referințelor permite izolarea scurgerilor într-un graf cu mii de obiecte.
  • Closure-urile — sursa principală de retain cycles în Swift, Memory Graph le afișează ca noduri {}.
  • Weak și unowned — soluții pentru ruperea ciclului, dar weak este preferat datorită siguranței la nil.
  • Verificarea regulată a Memory Graph după modificări de arhitectură previne regresia memoriei în proiect.

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și