Memory Graphは、Xcode Debug Navigatorのビジュアルツールで、アプリケーションのメモリ内にあるオブジェクトを相互参照とともにグラフ表示します。heap dumpとは異なり、Memory Graphは単なるオブジェクトのリストではなく、各ノードがオブジェクト、各エッジが参照(strong、weak、unowned)を表す有向参照グラフを表示します。Apple WWDC 2018によると、このツールを使用すると、heap dumpの生データを分析することなく、数秒でretain cyclesやメモリリークを視覚的に発見できます。
重要なポイント
Memory GraphはXcode Debug Navigatorのコンポーネント(Xcode 10、WWDC 2018で導入)で、デバッグ中のプロセスのメモリ内の全オブジェクトの有向グラフを構築します。グラフの各ノードはクラスインスタンス(Objective-CまたはSwift)、各エッジは別のオブジェクトへの参照です。エッジの色は参照タイプを示します:青=strong、緑=weak、灰=unowned。グラフはLLDBとObjective-C runtimeのデータに基づいて構築されるため、適切に動作させるにはアプリケーションをシンボル有効のDebug構成でコンパイルする必要があります。
仕組み:アプリケーションがブレークポイントで一時停止すると、XcodeはLLDBを介してruntimeにすべての生存オブジェクトとその参照を要求します。LLDBはobjc_getClassListを使用し、アロケーション領域を反復処理して完全なグラフを構築します。ARM64(Apple Silicon)では、追加のハードウェア手段を使用してアロケーションを追跡し、速度低下を防ぎます。グラフの構築時間はヒープサイズに依存します:標準的な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を検出し、次にInstruments Allocations経由のheap dumpで正確なretained sizeを測定します。objc.ioによると、両方のメソッドの組み合わせでメモリリークシナリオの95%をカバーできます。
Retain cycleは、2つ以上のオブジェクトがstrong参照で互いに保持し合い、閉じたループを形成する状況です。ARCは各オブジェクトのretain countがゼロにならないため、このようなループを解放できません。典型的な例:ViewControllerとViewで、Viewがself(ViewController)をキャプチャするクロージャへのstrong参照を持っている場合です。Memory Graphはこのようなループを環(サイクル)として表示し、素早く識別できるように強調表示します。
Xcodeがretain cycleを検出すると、オレンジ色の輪郭で強調表示し、Debug Navigatorに警告を表示します。サイクルをクリックすると、閉じたループを形成する参照のチェーンが表示されます。開発者はどのstrongエッジをweakにするべきかを判断するだけで済みます — 通常は子オブジェクトから親への参照(例: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はクロージャからViewControllerへの緑色のエッジを表示し、retain cycleは解消されます。
// 修正されたコード — selfの弱いキャプチャ
service.fetchData { [weak self] data in
guard let self else { return }
self.updateUI(data)
}
Memory Graph Debuggerのインターフェースは3つのパネルで構成されています:左側 — インスタンス数とともに全生存オブジェクトのリスト(クラス別にグループ化);中央 — ドラッグ可能なノードを持つビジュアルグラフ;右側 — 選択したオブジェクトまたはエッジのインスペクタ。オブジェクトリストには、クラスアイコン、メモリ内のインスタンス数、総retained size、ヒープ全体に対する割合が表示されます。クラス名によるフィルタリングは正規表現をサポートしています。
グラフのノードは読みやすくするためにドラッグできます。ノードをダブルクリックすると、オブジェクトの詳細情報が開きます:タイプと値を含むすべてのプロパティ、各プロパティのコールスタック(backtrace)、retain/release履歴。Backtraceは重要な機能です:オブジェクトへの参照を確立した正確なコード行を示します。これにより、すべてのコードを手動でレビューすることなくリークの原因を特定できます。
複雑なグラフの場合、XcodeはLayout → HierarchicalまたはClusterによる自動レイアウトを提供します。階層レイアウトはルートオブジェクトを上部に、子オブジェクトを下部に配置し、チェーンの検索を容易にします。クラスターグルーピングは関連オブジェクトをクラスターにグループ化し、グラフに複数の孤立したグループが含まれる場合に便利です。Appleによると、ほとんどのアプリケーションでは階層レイアウトが推奨されています — 直感的で、視覚的分析にかかる時間が短くなります。
// Memory Graphが内部で使用するLLDBコマンド
(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:Debug NavigatorのMemory Graphボタンをクリック — Xcodeがグラフを構築します。段階3:左パネルでオレンジ色のretain cycle警告を確認します。段階4:疑わしいオブジェクトについては、Show only cyclesオプションを使用 — 循環参照に関連するノードのみが表示されます。
retain cycleが見つかったら、サイクルのエッジをクリックしてインスペクタパネルを開きます。Backtraceセクションには、この参照が確立された時点のコールスタックが表示されます。たとえば、エッジがクロージャからselfに至る場合、backtraceはどのメソッドのどのコード行でクロージャが作成されたかを示します。これにより推測の必要がなくなります — 問題のある参照が作成されたポイントを即座に確認できます。WWDC Labsによると、backtrace分析により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はselfがnilの場合クラッシュを引き起こす可能性があります
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を使用しています。主要なアーキテクチャ変更(新しいdelegate、クロージャ、NotificationCenterサブスクリプションの追加)のたびにMemory Graphを確認します。典型的なシナリオを実行し、オブジェクトが正しく解放され、retain cyclesがないことを確認するだけです。これには2~3分かかりますが、その後の数時間のデバッグを防ぐことができます。
XcodeのMemory Report(Debug Navigatorタブ)は、リアルタイムのメモリ使用量グラフを表示します。Memory Graphと一緒に使用します:メモリ使用量が急増したときにMemory Graphを開きます。たとえば、画像を読み込むセルを含む長いリストをスクロールする場合、Memory Graphはどのオブジェクトが作成され、どのオブジェクトが解放されているかを示します。オブジェクトの数が減少せずに増加する場合 — クラッシュを引き起こす前に確認できる潜在的なリークです。Appleによると、Memory Graph + Memory Reportの組み合わせは、Xcode 12以降のすべてのiOS開発者に推奨されるワークフローです。
// 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;
クロージャに特に注意してください — Swiftで最も一般的なretain cycleの原因です。オブジェクトのプロパティとして保存されるクロージャ内でselfをキャプチャすると、古典的なサイクルが形成されます。Memory Graphはこれを、キャプチャされたオブジェクトに青いエッジで接続されたクロージャ({}シンボルのノード)として表示します。特に非同期呼び出し、GCD、Combine、SwiftUIで使用されるすべてのクロージャを定期的に確認してください。Point-Freeの統計によると、Swiftプロジェクトでのリークの90%はselfをキャプチャするクロージャに関連しています。
よくある質問
Memory GraphはObjective-C runtimeを使用するため、両方の言語で動作します。ObjCと互換性のあるSwiftオブジェクト(@objcがマークされたNSObjectサブクラス)は完全に表示されます。ObjCブリッジングのない純粋なSwift構造体やクラスは制限付きで表示されます。
オブジェクトはObjective-C runtimeに登録されている必要があります。Swiftの値型(struct、enum)は表示されません。クラスがNSObjectを継承しているか、Memory Graphで表示するために@objc属性を使用していることを確認してください。
青 — strong参照、オブジェクトを保持します。緑 — weak参照、ライフサイクルに影響しません。灰 — unowned参照。Retain cycleは青いエッジのみから形成されます。
グラフの構築はアプリケーションを1~3秒間一時停止し、Xcodeのメモリ消費を一時的に200~500 MB増加させる可能性があります。検査はブレークポイントの一時停止中に行われるため、アプリケーション自体は遅くなりません。
Xcodeはグラフの直接エクスポートをサポートしていません。ドキュメント用のスクリーンショット、またはプログラムによるデータ抽出にはlldbスクリプトheap.find_variableを使用してください。詳細な分析には、heap dumpとともにInstruments Allocationsを使用してください。
まとめ
{}ノードとして表示します。ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。