Memory Graph: шта је то, граф објеката и откривање цикличних референци

Аутор: IT Sectr Објављено: 2026-05-07 Време читања: 10 мин

Memory Graph — визуелни алат Xcode Debug Navigator-а који приказује граф објеката у оперативној меморији апликације са њиховим међусобним референцама. За разлику од heap dump-а, Memory Graph не показује само листу објеката, већ усмерени граф референци, где је сваки чвор објекат, а свака ивица референца (strong, weak, unowned). Према подацима Apple WWDC 2018, алат омогућава визуелно откривање retain cycles-а и цурења меморије за неколико секунди, без потребе за анализом сирових података heap dump-а.

Главно

  • Memory Graph — визуелни граф објеката у меморији Xcode-а, који приказује референце између објеката у реалном времену.
  • Retain cycle се открива по затвореном контуру у графу — два или више објеката упућују један на другог јаким референцама.
  • Backtrace за сваку ивицу графа показује где и када је постављена референца, поједностављујући проналажење извора цурења.
  • Филтрирање по имену класе и типу референце (strong/weak) омогућава брзо изоловање проблематичних објеката.
  • Интеграција са Memory Report-ом у Xcode-у омогућава праћење промене потрошње меморије у реалном времену.

Шта је Memory Graph и како ради

Memory Graph — то је компонента Xcode Debug Navigator-а (појавила се у Xcode 10, WWDC 2018) која гради усмерени граф свих објеката у меморији процеса који се дебагује. Сваки чвор графа је инстанца класе (Objective-C или Swift), свака ивица је референца ка другом објекту. Боја ивице указује на тип референце: плаве — strong, зелене — weak, сиве — unowned. Граф се гради на основу података LLDB-а и Objective-C runtime-а, због чега за исправан рад апликација мора бити компајлирана у Debug конфигурацији са укљученим симболима.

Принцип рада: када је апликација заустављена на breakpoint-у, Xcode преко LLDB-а захтева од runtime-а све живе објекте и њихове референце. LLDB користи objc_getClassList и итерацију кроз алокационе регионе за изградњу потпуног графа. На ARM64 (Apple Silicon) додатно се користе хардверска средства за праћење алокације без успоравања. Време изградње графа зависи од величине heap-а: за типичну iOS апликацију (50–200 MB) граф се гради за 1–3 секунде.

Према подацима Apple-а, Memory Graph је једини алат који може визуелизовати retain cycles без модификације кода или додавања инструментације. За разлику од Instruments Leaks-а, Memory Graph ради у реалном времену унутар Xcode-а и не захтева засебно покретање профилера. То га чини алатом првог избора за брзу дијагностику цурења меморије у процесу развоја.

Чим се Memory Graph разликује од heap dump-а

Heap dump даје табелу свих објеката са бројевима (shallow size, retained size) — оптималан је за квантитативну анализу. Memory Graph даје визуелну слику веза — оптималан је за проналажење цикличних референци. Алати се допуњују: прво Memory Graph за брзо откривање retain cycles-а, затим heap dump преко Instruments Allocations-а за прецизно мерење retained size-а. Према искуству objc.io, комбинација две методе покрива 95% сценарија цурења меморије.

Откривање retain cycles-а помоћу Memory Graph-а

Retain cycle — ситуација када два или више објеката држе један другог јаким референцама, формирајући затворени контур. ARC не може ослободити такав контур, јер retain count сваког објекта никада не достиже нулу. Класичан пример: ViewController и View, где View има strong reference на closure који захвата self (ViewController). Memory Graph приказује такве контуре у облику прстенова (циклуса), истичући их за брзу идентификацију.

Када Xcode открије retain cycle, истиче га наранџастим контуром и приказује упозорење у Debug Navigator-у. Кликом на циклус видите ланац референци који формира затворени контур. Програмеру остаје да одреди која од јаких ивица треба да буде слаба — обично је то референца из подређеног објекта ка надређеном (нпр. delegate или closure).

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

    override func viewDidLoad() {
        super.viewDidLoad()
        // ❌ Retain cycle: ViewController → сервис → 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
    }
}

У Memory Graph-у ћете видети троугао: ViewController → DataService → closure → ViewController. Решење — слабо захватање self-а: [weak self]. Након исправке, Memory Graph ће показати зелену ивицу од closure-а ка ViewController-у, а retain cycle ће нестати.

swift
// Исправљен код — слабо захватање self-а
service.fetchData { [weak self] data in
    guard let self else { return }
    self.updateUI(data)
}

Интерфејс Memory Graph Debugger-а у Xcode-у

Интерфејс Memory Graph Debugger-а се састоји од три панела: леви — листа свих живих објеката (груписаних по класама) са бројем инстанци; централни — визуелни граф са превлачивим чворовима; десни — инспектор изабраног објекта или ивице. У листи објеката приказују се: икона класе, број инстанци у меморији, укупни retained size и проценат целог heap-а. Филтрирање по имену класе подржава регуларне изразе.

Навигација по графу

Чворови графа се могу превлачити ради побољшања читљивости. Двоструки клик на чвор отвара детаљне информације о објекту: сва његова својства са типовима и вредностима, стек позива (backtrace) за свако својство и историју retain/release. Backtrace — кључна функција: показује која тачно линија кода је поставила референцу на објекат. Ово омогућава проналажење извора цурења без ручног прегледања целог кода.

За сложене графове Xcode обезбеђује аутоматски распоред кроз Layout → Hierarchical (хијерархијски) или Cluster (кластерски). Хијерархијски распоред поставља коренске објекте на врх, подређене — на дно, поједностављујући проналажење ланаца. Кластерски груписање повезане објекте у кластере, што је згодно када граф садржи неколико изолованих група. Према подацима Apple-а, за већину апликација препоручује се хијерархијски распоред — интуитиван је и захтева мање времена за визуелну анализу.

lldb
// LLDB команде које 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

Анализа графа: проналажење и отклањање цурења

Систематски приступ анализи Memory Graph-а укључује неколико фаза. Фаза 1: покрените апликацију, извршите сценарио који потенцијално изазива цурење (отворите/затворите екран, извршите мрежни захтев). Фаза 2: притисните дугме Memory Graph у Debug Navigator-у — Xcode ће изградити граф. Фаза 3: проверите наранџаста упозорења retain cycles-а у левом панелу. Фаза 4: за сумњиве објекте користите опцију Show only cycles — приказаће се само чворови који учествују у цикличним референцама.

Коришћење backtrace-а за проналажење извора

Када је retain cycle пронађен, кликните на ивицу циклуса и отворите панел инспектора. У секцији Backtrace приказан је стек позива у тренутку када је ова референца постављена. На пример, ако ивица води од closure-а ка self-у, backtrace ће показати у ком методу и на којој линији кода је closure креиран. Ово елиминише потребу за погађањем — одмах видите тачку креирања проблематичне референце. Према подацима WWDC Labs, анализа backtrace-а смањује време дијагностике retain cycle-а са 15–20 минута на 2–3 минута.

swift
class ProfileViewController: UIViewController {
    var profileView: ProfileView!

    override func viewDidLoad() {
        super.viewDidLoad()
        profileView = ProfileView()
        // Memory Graph ће показати retain cycle овде
        profileView.onTap = { [unowned self] in
            // ⚠️ unowned може изазвати crash при nil self-у
            self.navigateToDetail()
        }
    }

    func navigateToDetail() { }
}

// ✅ Исправно: [weak self] + guard let self
profileView.onTap = { [weak self] in
    guard let self else { return }
    self.navigateToDetail()
}

Филтрирање непотребних објеката

Memory Graph може приказивати хиљаде објеката, отежавајући претрагу. Користите филтере у левом панелу: унесите име класе (нпр. ProfileViewController) да бисте приказали само инстанце те класе. Затим изаберите инстанцу која је требало да буде ослобођена (ако је екран затворен, а објекат је остао). Примените Show Reachable From — приказаће се само референце релевантне за овај објекат, скривајући остатак графа.

Практични савети за коришћење Memory Graph-а

Искусни програмери користе Memory Graph не само за проналажење цурења, већ и за проактивну контролу меморије. Проверавајте Memory Graph након сваке веће промене архитектуре — додавања новог delegate-а, closure-а или претплате на NotificationCenter. Довољно је извршити типичан сценарио и уверити се да се објекти правилно ослобађају, а retain cycles не постоје. Ово траје 2–3 минута, али спречава сате каснијег дебаговања.

Комбинација са Memory Report-ом

Memory Report у Xcode-у (картица Debug Navigator) приказује графикон потрошње меморије у реалном времену. Користите га заједно са Memory Graph-ом: отварајте Memory Graph при наглом порасту потрошње. На пример, при скроловању дугачке листе са ћелијама које учитавају слике, Memory Graph ће показати који објекти се креирају, а који ослобађају. Ако број објеката расте без смањења — то је потенцијално цурење, видљиво пре него што доведе до crash-а. Према подацима Apple-а, комбинација Memory Graph + Memory Report је препоручени workflow за све iOS програмере, почев од Xcode 12.

objective-c
// Пример цурења у Objective-C-у преко делегације
@interface DownloadManager : NSObject
@property (strong) id delegate; // ❌ Треба да буде weak!
@end

@implementation DownloadManager
// Memory Graph ће показати retain cycle:
// ViewController → DownloadManager.delegate → ViewController
@end

// Исправка: weak property
@property (weak) id delegate;

Профилисање closure-а

Посебну пажњу посветите closure-има — најчешћем извору retain cycles-а у Swift-у. При захватању self-а унутар closure-а који се чува као својство објекта, формира се класичан циклус. Memory Graph то приказује као closure (чвор са симболом {}), повезан плавим ивицама са захваћеним објектима. Редовно проверавајте све closure-е, посебно оне који се користе у асинхроним позивима, GCD-у, Combine-у и SwiftUI-ју. Према статистици Point-Free, 90% цурења у Swift пројектима је повезано са closure-има који захватају self.

Често постављана питања

Да ли Memory Graph ради само за Objective-C или и за Swift?

Memory Graph ради за оба језика, јер користи Objective-C runtime. Swift објекти компатибилни са ObjC-ом (наследници NSObject-а, означени са @objc) приказују се у потпуности. Чисте Swift структуре и класе без ObjC моста видљиве су ограничено.

Зашто Memory Graph не приказује неке објекте?

Објекти морају бити регистровани у Objective-C runtime-у. Swift value types (struct, enum) се не приказују. Уверите се да класа наслеђује NSObject или користи атрибут @objc за видљивост у Memory Graph-у.

Како тумачити боје ивица у графу?

Плава — strong reference, држи објекат. Зелена — weak reference, не утиче на животни циклус. Сива — unowned reference. Retain cycle се формира само од плавих ивица.

Да ли Memory Graph успорава апликацију?

Изградња графа зауставља апликацију на 1–3 секунде и може привремено повећати потрошњу меморије Xcode-а за 200–500 MB. Сама апликација не успорава, јер се инспекција одвија током паузе на breakpoint-у.

Може ли се Memory Graph извести за анализу?

Xcode не подржава директан извоз графа. Користите снимак екрана за документацију или lldb скрипт heap.find_variable за програмско издвајање података. За детаљну анализу користите Instruments Allocations са heap dump-ом.

Закључци

  • Memory Graph — визуелни алат Xcode-а за приказ графа објеката у меморији са њиховим референцама.
  • Retain cycle се приказује као затворени контур од плавих (strong) ивица — Xcode га истиче наранџастом.
  • Backtrace за сваку ивицу графа показује тачно место у коду где је креирана проблематична референца.
  • Филтрирање по класама и типу референци омогућава изоловање цурења у графу са хиљадама објеката.
  • Closure-и — главни извор retain cycles-а у Swift-у, Memory Graph их приказује као чворове {}.
  • Weak и unowned — решења за прекид циклуса, али weak је пожељнији због сигурности при nil-у.
  • Редовна провера Memory Graph-а након промена архитектуре спречава регресију меморије у пројекту.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође