Memory Graph — wizualne narzędzie Xcode Debug Navigator, wyświetlające graf obiektów w pamięci operacyjnej aplikacji wraz z ich wzajemnymi referencjami. W przeciwieństwie do zrzutu sterty (heap dump), Memory Graph pokazuje nie tylko listę obiektów, ale skierowany graf referencji, gdzie każdy węzeł to obiekt, a każda krawędź to referencja (strong, weak, unowned). Według danych Apple WWDC 2018, narzędzie pozwala wizualnie wykrywać retain cycles i wycieki pamięci w ciągu sekund, bez konieczności analizy surowych danych zrzutu sterty.
Najważniejsze
Memory Graph — to komponent Xcode Debug Navigator (pojawił się w Xcode 10, WWDC 2018), który buduje skierowany graf wszystkich obiektów w pamięci debugowanego procesu. Każdy węzeł grafu to instancja klasy (Objective-C lub Swift), każda krawędź to referencja do innego obiektu. Kolor krawędzi wskazuje typ referencji: niebieskie — strong, zielone — weak, szare — unowned. Graf jest budowany na podstawie danych LLDB i Objective-C runtime, dlatego do poprawnego działania aplikacja musi być skompilowana w konfiguracji Debug z włączonymi symbolami.
Zasada działania: gdy aplikacja jest zatrzymana na breakpoincie, Xcode przez LLDB pyta runtime o wszystkie żywe obiekty i ich referencje. LLDB używa objc_getClassList i iteracji po regionach alokacji do zbudowania pełnego grafu. Na ARM64 (Apple Silicon) dodatkowo wykorzystywane są sprzętowe mechanizmy do śledzenia alokacji bez spowalniania. Czas budowania grafu zależy od rozmiaru sterty: dla typowej aplikacji iOS (50–200 MB) graf buduje się w 1–3 sekund.
Według danych Apple, Memory Graph to jedyne narzędzie, które może wizualizować retain cycles bez modyfikacji kodu ani dodawania instrumentacji. W przeciwieństwie do Instruments Leaks, Memory Graph działa w czasie rzeczywistym wewnątrz Xcode i nie wymaga osobnego uruchamiania profilera. To czyni go narzędziem pierwszego wyboru do szybkiej diagnostyki wycieków pamięci w procesie programowania.
Zrzut sterty (heap dump) daje tabelę wszystkich obiektów z liczbami (shallow size, retained size) — jest optymalny do analizy ilościowej. Memory Graph daje wizualny obraz powiązań — jest optymalny do wyszukiwania cyklicznych referencji. Narzędzia uzupełniają się: najpierw Memory Graph do szybkiego wykrywania retain cycles, następnie zrzut sterty przez Instruments Allocations do dokładnego pomiaru retained size. Według doświadczeń objc.io, kombinacja obu metod pokrywa 95% scenariuszy wycieków pamięci.
Retain cycle — sytuacja, gdy dwa lub więcej obiektów utrzymuje się nawzajem silnymi referencjami, tworząc zamkniętą pętlę. ARC nie może zwolnić takiej pętli, ponieważ retain count każdego obiektu nigdy nie osiąga zera. Klasyczny przykład: ViewController i View, gdzie View ma strong reference do closure, który przechwytuje self (ViewController). Memory Graph wyświetla takie pętle w postaci pierścieni (cykli), podświetlając je w celu szybkiej identyfikacji.
Gdy Xcode wykryje retain cycle, wyróżnia go pomarańczowym konturem i pokazuje ostrzeżenie w Debug Navigator. Klikając na cykl, widzisz łańcuch referencji tworzących zamkniętą pętlę. Programiście pozostaje określić, która z silnych krawędzi powinna być słaba — zazwyczaj jest to referencja z obiektu potomnego do nadrzędnego (np. delegate lub closure).
class ViewController: UIViewController {
let service = DataService()
override func viewDidLoad() {
super.viewDidLoad()
// ❌ Cykl retain: ViewController → serwis → domknięcie → 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
}
}
W Memory Graph zobaczysz trójkąt: ViewController → DataService → closure → ViewController. Rozwiązaniem jest słabe przechwycenie self: [weak self]. Po poprawce Memory Graph pokaże zieloną krawędź od closure do ViewController, a retain cycle zniknie.
// Poprawiony kod — słabe przechwycenie self
service.fetchData { [weak self] data in
guard let self else { return }
self.updateUI(data)
}
Interfejs Memory Graph Debugger składa się z trzech paneli: lewy — lista wszystkich żywych obiektów (pogrupowanych według klas) z liczbą instancji; centralny — wizualny graf z przeciągalnymi węzłami; prawy — inspektor zaznaczonego obiektu lub krawędzi. Na liście obiektów wyświetlane są: ikona klasy, liczba instancji w pamięci, całkowity retained size oraz procent całej sterty. Filtrowanie według nazwy klasy obsługuje wyrażenia regularne.
Węzły grafu można przeciągać w celu poprawy czytelności. Podwójne kliknięcie na węźle otwiera szczegółowe informacje o obiekcie: wszystkie jego właściwości z typami i wartościami, stos wywołań (backtrace) dla każdej właściwości oraz historię retain/release. Backtrace — kluczowa funkcja: pokazuje, który dokładnie wiersz kodu ustanowił referencję do obiektu. Pozwala to znaleźć źródło wycieku bez ręcznego przeglądania całego kodu.
Dla złożonych grafów Xcode zapewnia automatyczny układ poprzez Layout → Hierarchical (hierarchiczny) lub Cluster (klastrowy). Układ hierarchiczny umieszcza obiekty nadrzędne u góry, potomne — na dole, ułatwiając wyszukiwanie łańcuchów. Układ klastrowy grupuje powiązane obiekty w klastry, co jest wygodne, gdy graf zawiera kilka izolowanych grup. Według danych Apple, dla większości aplikacji zalecany jest układ hierarchiczny — jest intuicyjny i zajmuje mniej czasu na analizę wizualną.
// Polecenia LLDB używane przez Memory Graph pod maską
(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
Systematyczne podejście do analizy Memory Graph obejmuje kilka etapów. Etap 1: uruchom aplikację, wykonaj scenariusz, który potencjalnie powoduje wyciek (otwórz/zamknij ekran, wykonaj zapytanie sieciowe). Etap 2: naciśnij przycisk Memory Graph w Debug Navigator — Xcode zbuduje graf. Etap 3: sprawdź pomarańczowe ostrzeżenia retain cycles w lewym panelu. Etap 4: dla podejrzanych obiektów użyj opcji Show only cycles — wyświetlą się tylko węzły wchodzące w skład cyklicznych referencji.
Gdy retain cycle zostanie znaleziony, kliknij na krawędź cyklu i otwórz panel inspektora. W sekcji Backtrace pokazany jest stos wywołań w momencie, gdy ta referencja została ustanowiona. Na przykład, jeśli krawędź prowadzi od closure do self, backtrace pokaże, w której metodzie i w którym wierszu kodu closure został utworzony. Eliminuje to konieczność zgadywania — od razu widzisz punkt utworzenia problematycznej referencji. Według danych WWDC Labs, analiza backtrace skraca czas diagnostyki retain cycle z 15–20 minut do 2–3 minut.
class ProfileViewController: UIViewController {
var profileView: ProfileView!
override func viewDidLoad() {
super.viewDidLoad()
profileView = ProfileView()
// Memory Graph pokaże retain cycle tutaj
profileView.onTap = { [unowned self] in
// ⚠️ unowned może spowodować crash przy nil self
self.navigateToDetail()
}
}
func navigateToDetail() { }
}
// ✅ Poprawnie: [weak self] + guard let self
profileView.onTap = { [weak self] in
guard let self else { return }
self.navigateToDetail()
}
Memory Graph może pokazywać tysiące obiektów, utrudniając wyszukiwanie. Używaj filtrów w lewym panelu: wpisz nazwę klasy (np. ProfileViewController), aby wyświetlić tylko instancje tej klasy. Następnie wybierz instancję, która powinna zostać zwolniona (jeśli ekran jest zamknięty, a obiekt pozostał). Zastosuj Show Reachable From — wyświetlą się tylko referencje istotne dla tego obiektu, ukrywając resztę grafu.
Doświadczeni programiści używają Memory Graph nie tylko do wyszukiwania wycieków, ale także do proaktywnej kontroli pamięci. Sprawdzaj Memory Graph po każdej większej zmianie architektury — dodaniu nowego delegata, domknięcia lub subskrypcji do NotificationCenter. Wystarczy wykonać typowy scenariusz i upewnić się, że obiekty są poprawnie zwalniane, a retain cycles nie występują. Zajmuje to 2–3 minuty, ale zapobiega godzinom późniejszego debugowania.
Memory Report w Xcode (zakładka Debug Navigator) pokazuje wykres zużycia pamięci w czasie rzeczywistym. Używaj go razem z Memory Graph: otwieraj Memory Graph przy gwałtownym wzroście zużycia. Na przykład, podczas przewijania długiej listy z komórkami ładującymi obrazy, Memory Graph pokaże, które obiekty są tworzone, a które — zwalniane. Jeśli liczba obiektów rośnie bez spadku — to potencjalny wyciek, widoczny zanim doprowadzi do crasha. Według danych Apple, kombinacja Memory Graph + Memory Report to recommended workflow dla wszystkich programistów iOS, począwszy od Xcode 12.
// Przykład wycieku w Objective-C przez delegację
@interface DownloadManager : NSObject
@property (strong) id delegate; // ❌ Powinno być weak!
@end
@implementation DownloadManager
// Memory Graph pokaże retain cycle:
// ViewController → DownloadManager.delegate → ViewController
@end
// Poprawka: weak property
@property (weak) id delegate;
Szczególną uwagę zwracaj na domknięcia (closures) — najczęstsze źródło retain cycles w Swift. Przy przechwytywaniu self wewnątrz domknięcia, które jest przechowywane jako właściwość obiektu, powstaje klasyczny cykl. Memory Graph wyświetla to jako domknięcie (węzeł z symbolem {}), połączone niebieskimi krawędziami z przechwyconymi obiektami. Regularnie sprawdzaj wszystkie closures, szczególnie używane w wywołaniach asynchronicznych, GCD, Combine i SwiftUI. Według statystyk Point-Free, 90% wycieków w projektach Swift jest związanych z domknięciami przechwytującymi self.
Często zadawane pytania
Memory Graph działa dla obu języków, ponieważ używa Objective-C runtime. Obiekty Swift zgodne z ObjC (dziedziczące po NSObject, oznaczone @objc) są wyświetlane w pełni. Czyste struktury Swift i klasy bez mostkowania ObjC są widoczne ograniczenie.
Obiekty muszą być zarejestrowane w Objective-C runtime. Swift value types (struct, enum) nie są wyświetlane. Upewnij się, że klasa dziedziczy po NSObject lub używa atrybutu @objc dla widoczności w Memory Graph.
Niebieski — strong reference, utrzymuje obiekt. Zielony — weak reference, nie wpływa na cykl życia. Szary — unowned reference. Retain cycle tworzy się tylko z niebieskich krawędzi.
Budowanie grafu wstrzymuje aplikację na 1–3 sekundy i może tymczasowo zwiększyć zużycie pamięci Xcode o 200–500 MB. Sama aplikacja nie zwalnia, ponieważ inspekcja odbywa się podczas pauzy na breakpoincie.
Xcode nie obsługuje bezpośredniego eksportu grafu. Użyj zrzutu ekranu do dokumentacji lub skryptu lldb heap.find_variable do programowego wydobycia danych. Do szczegółowej analizy użyj Instruments Allocations ze zrzutem sterty.
Podsumowanie
{}.Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również