ios-memgraph-analysis について
明確なオブジェクトのライフタイムを基準に、到達不能なリーク、到達可能な増加、キャッシュ、ヒープ回帰を見分ける iOS メモリグラフ分析スキルです。生の証拠を保持し、修正後も同じ型と所有経路を検証します。
主な機能
- 01解放境界の定義とグラフの取得
- 02生の出力を保持した有界サマリーの作成
- 03リーク、到達可能な増加、キャッシュの分類
- 04アプリ所有オブジェクトと所有経路の追跡
- 05同じライフタイム条件で修正を再検証
活用シーン
- 01画面を閉じた後も残るオブジェクトの調査
- 02リーク報告がない場合のヒープ増加の分析
- 03キャッシュ上限と削除動作の確認
- 04修正前後の所有経路の比較
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill ios-memgraph-analysisインストール後、Claude Code で/ios-memgraph-analysisを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/reachable-growth.mdscripts/capture_sim_memgraph.pysummarize_memgraph.py詳細
iOS Memgraph 分析
メモリーグラフを使って、定義したライフタイム境界を越えてメモリーが残る理由を証明します。 到達不能なリークと到達可能な増加を区別し、生のツール出力を保持し、修正後に同じアプリ所有の型と所有権パスを確認します。
目次
境界
このスキルは、.memgraph のキャプチャと、コマンドラインによる所有権・増加の分析を担当します。
インタラクティブなグラフや割り当てタイムラインが主な作業となる場合は、Memory Graph Debugger または Instruments を使用してください。ランタイムのエビデンスでライフタイムまたはパスが特定された後に限り、クロージャーによるキャプチャが疑われる場合はソースを確認してください。
エビデンスモデル
次の状態をひとまとめにしないでください。
- 到達不能なリーク: 割り当てられたメモリーが、ライブなルートからのパスを持たなくなっています。 孤立した強参照サイクルは到達不能でも、メモリーを消費し続けることがあります。
- 到達可能だが放棄された状態: ライブなルートが、ユーザーフローで不要になったオブジェクトを保持し続けています。
leaksがゼロと正しく報告する場合もあります。 - 想定内のキャッシュまたはプール: メモリーが意図的に保持されています。上限、削除動作、メモリー負荷への応答に基づいて判断する必要があります。
- ヒープのリグレッションまたは断片化: リークがなくても、より多くの、またはより大きな割り当てが残存したり、ダーティページが十分に活用されなかったりすることで、フットプリントが増加します。
Apple のリークスキャナーは保守的なポインター検出と不完全な型メタデータを使用します。カウントは変動することがあり、結果がゼロでも所有権の問題がないとは証明できません。強力なエビデンスでは、想定されるライフタイム、アプリ所有の型または割り当て、そして信頼できるパスまたは単独での再現を特定します。
ワークフロー
1. キャプチャ前にライフタイムを定義する
消滅すべきオブジェクトと、その有用なライフタイムを終わらせるイベントを明確にします。
たとえば、エディターを閉じ、保留中の保存処理が完了した後に、EditorViewModel が破棄されるべきとします。
決定的な一連の手順を記録します。
- 既知の状態を起動または復元します。
- 任意でベースライングラフを取得します。
- 機能のフローを実行します。
- 想定される解放境界を越えます。
- 正当な非同期クリーンアップを待ちます。
- フロー後のグラフを取得します。
ビルド、シミュレーター/デバイス、データ、Malloc Stack Logging の設定、繰り返し回数を一定に保ちます。Malloc Stack Logging は有用な割り当てバックトレースを追加しますが、オーバーヘッドも発生します。同じ設定の実行結果のみを比較してください。
2. プロセスを推測せずにグラフをキャプチャする
Xcode では Memory Graph Debugger からグラフをエクスポートできます。実行中のシミュレーターアプリには、このスキルのヘルパーを使用します。
mkdir -p /tmp/myapp-memory
mkdir /tmp/myapp-memory/run-01
python3 scripts/capture_sim_memgraph.py \
--bundle-id com.example.MyApp \
--output-dir /tmp/myapp-memory/run-01 \
--pretty > /tmp/myapp-memory/run-01/capture.json
実行ごとの mkdir は、キャプチャディレクトリがすでに存在する場合に失敗しなければなりません。古いエビデンスと再試行の結果を混在させず、新しい実行名を使用してください。
複数のシミュレーターが起動している場合は、--udid を渡します。ヘルパーが受け付けるのは、launchd ラベルと PID がそれぞれ完全一致するものが1つだけの場合です。一致がゼロ件または複数件の場合はエラーになります。ホストの leaks --outputGraph コマンドを実行し、stdout/stderr を保持してマニフェストを書き込みます。これを pgrep | head -1 や部分一致に置き換えないでください。
キャプチャ中はプロセスが一時停止します。キャプチャにかかる時間をパフォーマンスデータとして使用しないでください。
3. 生の出力を保持し、上限付きのサマリーを作成する
MEMGRAPH=$(jq -er \
'select(.status == "captured") | .memgraph | select(type == "string" and length > 0)' \
/tmp/myapp-memory/run-01/capture.json)
test -s "$MEMGRAPH"
python3 scripts/summarize_memgraph.py \
"$MEMGRAPH" \
--artifact-dir /tmp/myapp-memory/run-01/analysis-raw \
--app-image 'MyApp|MyFeatureKit' \
--trace-limit 3 --group-by-type --pretty \
> /tmp/myapp-memory/run-01/analysis.json
保持されたキャプチャレポートから正確なグラフパスを読み取ってください。タイムスタンプ付きのファイル名を推測してはいけません。このヘルパーは専用の生アーティファクト用ディレクトリを作成し、そのディレクトリの再利用を拒否し、leaks --list を実行して、出力テキストの保守的なサブセットのみを解析します。--app-image は候補行を示すものであり、所有権を証明するものではありません。--trace-limit は上限付きの leaks --traceTree=<address> クエリを実行します。個別のリークアドレスよりも集約されたルートパスが役立つ場合は、--reference-tree を追加してください。--group-by-type を指定すると、そのリファレンスツリーのクエリも同じ呼び出しでグループ化されます。leaks の終了ステータス 0 と 1 は引き続き解析可能です。プライマリステータスが 1 を超える場合はサマリーに失敗します。一方、オプションクエリの失敗は、無効である旨の警告とともに保持されますが、有効なプライマリサマリーは破棄されません。
Apple は、これらのテキスト形式を安定した機械処理用スキーマとして公開していません。解析の警告が出た場合は、生アーティファクトを確認してください。望みどおりの結果が出るまでパーサーを緩める理由にしてはいけません。
4. アプリが所有する最初の対処可能なエッジを見つける
アプリが所有するリーク型またはアロケーションスタックから調査を始めます。次を確認してください。
- リークのオブジェクトグラフと、存在する場合は Malloc Stack Logging のバックトレース。
- 1 つのアドレスを参照するオブジェクトについて、上限付きの
--traceTree=<address>。 - 繰り返し現れる型をまとめ、保持されたペイロードを明らかにする
--groupByType。 - 原因となるアドレスが不明な場合の、上から下へのビューを示す
--referenceTree。 - アプリが制御する最初の強参照エッジを示すソースコード。
到達不能な自己循環には、traceTree 内にライブなルートが存在しないことがあります。グループ化されたリークグラフに加え、ソースコードでの検証を行うか、動作を分離した再現ケースにしてください。グラフにないルートパスを決して捏造してはいけません。
5. leaks が空の場合に増加を調査する
条件をそろえたベースラインとフロー後のグラフを使い、増加している領域を特定し、オブジェクト型を比較したうえで、疑わしいアドレスをアプリが所有するエッジまでたどります。証拠として確認すべきなのは、同じライフタイムにわたる、到達可能な状態での持続的な増加です。RSS の低下や、1 回だけの大きなスナップショットではありません。リークが空の場合に限り、reachable-growth.md を読み込んでください。このファイルには、順序付けされた vmmap、heap、leaks、malloc_history のクエリと、ログ記録に依存する代替手段が記載されています。
6. 同じライフタイムで修正して検証する
最も限定的な所有権の修正を優先してください。意図しない強参照エッジを切る、そのオブジェクトを所有する処理をキャンセルする、オブザーバーを削除する、キャッシュに上限を設けて追い出す、または最後に使った後で大きなバッファを解放します。参照先が正当に nil になり得る場合は weak を使い、ライフタイムが保証されていることが証明できる場合に限って unowned を使ってください。
同一のフローを繰り返してください。同じアプリ所有の型またはパスが消えるか、それに起因するフロー前後の増加が複数回の実行を通じて解消されれば、修正を裏付ける証拠になります。RSS の低下、グラフファイルの縮小、または集計されたリーク数の低下だけでは証明になりません。
所有権の判断
| 証拠 | 次のアクション |
|---|---|
| ルート循環内にアプリの型がある | 両方の強参照エッジとアロケーションスタックを確認します。 |
| リークアドレスのルートがない | グループ化された循環の証拠を確認し、フローを分離します。 |
| ライブなルートが、閉じた機能の状態を保持している | パスをたどり、アプリが所有する最初のエッジを特定します。 |
| リークはゼロだが malloc の増加を繰り返す | ベースラインとフロー後のヒープオブジェクトを比較します。 |
| フレームワークオブジェクトが支配的である | アプリが作成した所有者、入力、または呼び出し頻度を特定します。 |
| 増加が文書化された上限で安定する | 変更する前に、追い出し/メモリプレッシャー時の動作をテストします。 |
よくある間違い
leaksの結果が一度ゼロだっただけで、アプリにリークがないと宣言する。- 曖昧な一覧から最初の PID や Simulator を選択する。
- 比較対象の片方だけで Malloc Stack Logging を有効にする。
- パーサーのベストエフォートによる型の列を、API の保証として扱う。
- アプリとの関連箇所を見つけずに、巨大な参照ツリーをレポートに貼り付ける。
- ライフタイムを考えずに、すべてのクロージャーを
[weak self]で修正する。 - 対象のライフタイムと所有権の経路を証明せず、グラフのサイズ、RSS、または合計数の変化だけで成功を主張する。
レビューチェックリスト
- オブジェクトと、解放されると予想される境界が明確になっている。
- ベースラインとフロー後のグラフで、ビルド、実行時のターゲット、データの状態、決定的なフロー、クリーンアップの待機時間、繰り返し回数、Malloc Stack Logging の設定が同じである。
- Simulator、バンドル識別子、プロセスラベル、PID が明確である。
- 元のグラフと生のコマンド出力が保存されている。
- 現在インストールされているツールのヘルプで、バージョンによって異なるコマンド形式を確認している。
- リーク、到達可能な増加、想定されるキャッシュ、断片化を区別している。
- 指摘に、アプリが所有する型または割り当てと、妥当な経路が記載されている。
- 型メタデータの欠落や保守的スキャナーの制限を明示している。
- 修正によって、所有権またはライフタイムに関する原因を 1 つ変更している。
- 同じフローと証拠の確認を繰り返して検証している。
参考資料
leaksが空でも到達可能なメモリが増加する場合 — グラフの照合と、アドレスから所有者をたどるワークフロー- メモリ使用量に関する情報の収集
- メモリの問題の検出と診断 — WWDC21
- ヒープメモリの分析 — WWDC24