Memory Graph — візуальний інструмент Xcode Debug Navigator, що відображає граф об'єктів в оперативній пам'яті додатку з їхніми взаємними посиланнями. На відміну від heap dump, Memory Graph показує не просто список об'єктів, а спрямований граф посилань, де кожен вузол — об'єкт, а кожне ребро — посилання (strong, weak, unowned). За даними Apple WWDC 2018, інструмент дозволяє візуально виявляти retain cycles та витоки пам'яті за секунди, без необхідності аналізу сирих даних дампа купи.
Головне
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 та ітерацію по allocation-регіонах для побудови повного графа. На ARM64 (Apple Silicon) додатково використовуються апаратні засоби для відстеження allocation без уповільнення. Час побудови графа залежить від розміру купи: для типового додатку iOS (50–200 MB) граф будується за 1–3 секунди.
За даними Apple, Memory Graph — єдиний інструмент, який може візуалізувати retain cycles без модифікації коду або додавання інструментації. На відміну від Instruments Leaks, Memory Graph працює в реальному часі всередині Xcode і не вимагає окремого запуску профілювальника. Це робить його першим інструментом вибору для швидкої діагностики витоків пам'яті в процесі розробки.
Heap dump дає таблицю всіх об'єктів з числами (shallow size, retained size) — він оптимальний для кількісного аналізу. Memory Graph дає візуальну картину зв'язків — оптимальний для пошуку циклічних посилань. Інструменти доповнюють один одного: спочатку Memory Graph для швидкого виявлення retain cycles, потім heap dump через Instruments Allocations для точного вимірювання retained size. За досвідом objc.io, комбінація двох методів покриває 95% сценаріїв витоків пам'яті.
Retain cycle — ситуація, коли два або більше об'єктів утримують один одного сильними посиланнями, утворюючи замкнутий контур. ARC не може звільнити такий контур, оскільки retain count кожного об'єкта ніколи не досягає нуля. Класичний приклад: ViewController і View, де View має strong reference на closure, який захоплює self (ViewController). Memory Graph відображає такі контури у вигляді кілець (циклів), підсвічуючи їх для швидкої ідентифікації.
Коли Xcode виявляє retain cycle, він виділяє його помаранчевим контуром і показує попередження в Debug Navigator. Клацнувши по циклу, ви бачите ланцюжок посилань, що утворюють замкнутий контур. Розробнику залишається визначити, яке з strong-ребер має бути слабким — зазвичай це посилання з дочірнього об'єкта на батьківський (наприклад, delegate або closure).
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
}
}
У Memory Graph ви побачите трикутник: ViewController → DataService → closure → ViewController. Рішення — зробити захоплення self слабким: [weak self]. Після виправлення Memory Graph покаже зелене ребро від closure до ViewController, і retain cycle зникне.
// Виправлений код — слабке захоплення self
service.fetchData { [weak self] data in
guard let self else { return }
self.updateUI(data)
}
Інтерфейс Memory Graph Debugger складається з трьох панелей: ліва — список усіх живих об'єктів (згрупованих за класами) з кількістю екземплярів; центральна — візуальний граф з перетягуваними вузлами; права — інспектор вибраного об'єкта або ребра. У списку об'єктів відображаються: іконка класу, кількість екземплярів у пам'яті, загальний retained size та відсоток від усієї купи. Фільтрація за ім'ям класу підтримує регулярні вирази.
Вузли графа можна перетягувати для покращення читабельності. Подвійний клік по вузлу відкриває детальну інформацію про об'єкт: усі його властивості з типами та значеннями, стек викликів (backtrace) для кожної властивості та історію retain/release. Backtrace — ключова функція: він показує, який саме рядок коду встановив посилання на об'єкт. Це дозволяє знайти джерело витоку без ручного перегляду всього коду.
Для складних графів Xcode надає автоматичне розташування через Layout → Hierarchical (ієрархічне) або Cluster (кластерне). Ієрархічне розташування розміщує кореневі об'єкти зверху, дочірні — знизу, спрощуючи пошук ланцюжків. Кластерне групує пов'язані об'єкти в кластери, що зручно, коли граф містить кілька ізольованих груп. За даними Apple, для більшості додатків рекомендується ієрархічне розташування — воно інтуїтивно зрозуміле та займає менше часу на візуальний аналіз.
// Команди 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 — відобразяться лише вузли, що входять у циклічні посилання.
Коли retain cycle знайдено, клацніть по ребру циклу та відкрийте панель інспектора. У розділі Backtrace показано стек викликів у момент, коли це посилання було встановлено. Наприклад, якщо ребро веде від closure до self, backtrace покаже, в якому методі та на якому рядку коду closure був створений. Це усуває необхідність здогадуватися — ви одразу бачите точку створення проблемного посилання. За даними WWDC Labs, backtrace analysis скорочує час діагностики retain cycle з 15–20 хвилин до 2–3 хвилин.
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 після кожної великої зміни архітектури — додавання нового делегата, замикання або підписки на NotificationCenter. Достатньо виконати типовий сценарій і переконатися, що об'єкти звільняються коректно, а retain cycles відсутні. Це займає 2–3 хвилини, але запобігає годинам подальшого налагодження.
Memory Report у Xcode (вкладка Debug Navigator) показує графік споживання пам'яті в реальному часі. Використовуйте його разом з Memory Graph: відкривайте Memory Graph при різкому зростанні споживання. Наприклад, при скролі довгого списку з комірками, що завантажують зображення, Memory Graph покаже, які об'єкти створюються, а які — звільняються. Якщо кількість об'єктів зростає без зниження — це потенційний витік, видимий до того, як він призведе до crash. За даними Apple, комбінація Memory Graph + Memory Report — recommended workflow для всіх iOS-розробників, починаючи з Xcode 12.
// Приклад витоку в Objective-C через delegation
@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;
Особливу увагу приділяйте замиканням (closures) — найчастіше джерело retain cycles у Swift. При захопленні self всередині замикання, яке зберігається як властивість об'єкта, утворюється класичний цикл. Memory Graph відображає це як замикання (вузол із символом {}), з'єднане синіми ребрами з захопленими об'єктами. Регулярно перевіряйте всі closures, особливо ті, що використовуються в асинхронних викликах, GCD, Combine та SwiftUI. За статистикою Point-Free, 90% витоків у Swift-проектах пов'язані з замиканнями, що захоплюють self.
Часто задавані питання
Memory Graph працює для обох мов, оскільки використовує Objective-C runtime. Swift-об'єкти, сумісні з ObjC (спадкоємці NSObject, позначені @objc), відображаються повністю. Чисті Swift-структури та класи без ObjC-мостування видно обмежено.
Об'єкти повинні бути зареєстровані в Objective-C runtime. Swift value types (struct, enum) не відображаються. Переконайтеся, що клас успадковує NSObject або використовує атрибут @objc для видимості в Memory Graph.
Синій — strong reference, утримує об'єкт. Зелений — weak reference, не впливає на життєвий цикл. Сірий — unowned reference. Retain cycle утворюється тільки з синіх ребер.
Побудова графа призупиняє додаток на 1–3 секунди та може тимчасово збільшити споживання пам'яті Xcode на 200–500 MB. Сам додаток не сповільнюється, оскільки інспекція відбувається під час паузи на breakpoint.
Xcode не підтримує експорт графа безпосередньо. Використовуйте скріншот для документації або скрипт lldb heap.find_variable для програмного вилучення даних. Для детального аналізу використовуйте Instruments Allocations з дампом купи.
Підсумки
{}.Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також