mcpField
D
debugging-instruments

debugging-instruments

github.com/dpearson2699開発1.2k4.1k 回インストール13 週間前に更新
リポジトリ

debugging-instruments について

Appleプラットフォームの開発者向けに、LLDB、メモリ問題、ハング、ビルド失敗を扱うデバッグとInstrumentsのスキルガイドです。プロファイリングツールの選び方とxctraceによるコマンドライン記録も説明します。

主な機能

  1. 01LLDBのブレークポイントと状態確認
  2. 02メモリグラフとリーク調査
  3. 03メインスレッドのハング診断
  4. 04ビルドと依存関係の問題調査
  5. 05Instrumentsテンプレートの選択
  6. 06xctraceによるコマンドライン計測

活用シーン

  1. 01クラッシュや予期しない状態の追跡
  2. 02リークやメモリ増加の調査
  3. 03操作時のハングや処理負荷の分析
  4. 04ビルド失敗と性能低下の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill debugging-instruments

インストール後、Claude Code で/debugging-instrumentsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
instruments-guide.md
lldb-patterns.md

詳細

デバッグと Instruments

対話型グラフと Instruments によるトリアージはここで行います。詳細な .memgraph コマンドラインによる所有関係・増加分析と ETTrace の作業は、それぞれ専用のスキルに振り分けてください。

目次

LLDB によるデバッグ

まずは、簡潔で再現性の高いワークフローから始めます。

  1. Debug ビルドで問題を再現し、必要な範囲に絞ったブレークポイントで停止します。
  2. コードを実行せずにローカル変数を確認し、現在のスタックを記録します。
  3. 関連するフレームまたはスレッドに移動して、問題が発生している状態を確認します。
  4. 問題のある状態遷移がまだ不明な場合に限り、条件またはウォッチポイントを追加します。
(lldb) br set -f ViewModel.swift -l 42     # Stop at file and line
(lldb) v myLocal                           # Inspect without executing code
(lldb) po myObject                         # Use debugDescription when needed
(lldb) bt all                              # Capture every thread's backtrace
(lldb) frame select 3                      # Inspect a relevant frame
(lldb) br modify 1 -c "count > 10"         # Narrow a noisy breakpoint
(lldb) w set v self.score                  # Stop on an unexpected write

ローカル変数の値だけを確認する場合は、po ではなく v を使います。コードを実行せず、副作用を引き起こすこともありません。式の評価はプログラムの状態を実行または変更する可能性があり、ハードウェアウォッチポイントも限られているため、どちらも必要な場合に限って使用してください。

確認、ブレークポイント/ログポイント、式、ウォッチポイント、スレッドの移動、シンボリックブレークポイントに関するコマンド表の全体については、references/lldb-patterns.md を参照してください。

メモリのデバッグ

Memory Graph Debugger のワークフロー
  1. Debug 構成でアプリを実行します。
  2. リークの疑いがある状況を再現します(画面に移動してから戻ります)。
  3. Xcode のデバッグバーにある Memory Graph ボタンをタップします。
  4. 紫色の警告アイコンを探します。これはオブジェクトのリークを示しています。
  5. リークしたオブジェクトを選択し、参照グラフとバックトレースを確認します。

実行前に Malloc Stack Logging(Scheme > Diagnostics)を有効にしておくと、Memory Graph に割り当てのバックトレースが表示されます。

よくある保持サイクルのパターン

self を強参照でキャプチャするクロージャ:

// LEAK — closure holds strong reference to self
class ProfileViewModel {
    var onUpdate: (() -> Void)?

    func startObserving() {
        onUpdate = {
            self.refresh()  // strong capture of self
        }
    }
}

// FIXED — use [weak self]
func startObserving() {
    onUpdate = { [weak self] in
        self?.refresh()
    }
}

デリゲートへの強参照:

// LEAK — strong delegate creates a cycle
protocol DataDelegate: AnyObject {
    func didUpdate()
}

class DataManager {
    var delegate: DataDelegate?  // should be weak
}

// FIXED — weak delegate
class DataManager {
    weak var delegate: DataDelegate?
}

ターゲットを保持するタイマー:

// LEAK — Timer.scheduledTimer retains its target
timer = Timer.scheduledTimer(
    timeInterval: 1.0, target: self,
    selector: #selector(tick), userInfo: nil, repeats: true
)

// FIXED — use closure-based API with [weak self]
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
    self?.tick()
}
Instruments:Allocations と Leaks
  • Allocations テンプレート:時間経過に伴うメモリの増加を追跡します。「Mark Generation」機能を使うと、ユーザー操作(例:画面の表示/終了)の間に作成された割り当てを切り分けられます。
  • Leaks テンプレート:プロセスから到達できなくなった孤立した保持サイクルを含め、リークした割り当てを検出します。全体像を把握するため、Allocations と併せて実行してください。
  • アプリのモジュール名でフィルタリングし、システムの割り当てを除外します。

リークやメモリ増加のトリアージでは、ツールを組み合わせます。再現の前後に Allocations の Mark Generation を使用して保持された増加を確認し、次に Memory Graph Debugger でオブジェクトの所有関係を調べ、Malloc Stack Logging で割り当て時のコールスタックを取得します。

Malloc Stack Logging

Scheme > Run > Diagnostics > Malloc Stack Logging で有効にします。これにより割り当てのバックトレースが記録され、Memory Graph Debugger、Allocations インストゥルメント、エクスポートした .memgraph ファイルでオブジェクトの作成場所を確認できます。

# Inspect an exported memory graph from Xcode or Instruments
leaks MyApp.memgraph

ハングの診断

メインスレッドのハングを特定する

個別の操作では、100 ms 未満の遅延はほとんど気になりません。Apple の開発者向けツールでは、メインランループのビジー状態が 250 ms を超えると一般的に報告されますが、この報告しきい値は製品の目標値ではありません。数百ミリ秒でも反応が遅いと感じることがあります。一般的な検出ツールは次のとおりです。

  • Thread Checker(Xcode Diagnostics):メインスレッド以外からの UI 呼び出しを警告します
  • Thread Performance Checker:デバッグ中に優先度の逆転を報告します
  • デバイス上のハング検出:Developer Settings で、デバイス使用中に発生したハングを報告します
  • Time Profiler / CPU Profiler / Hitches:再現可能なハングをプロファイルします
  • os_signpost と OSSignposter:Instruments 用に区間を記録します
  • MetricKit のハング診断:本番環境でのハングを検出します(HangDiagnostic と iOS 26 との互換性については metrickit スキルを参照)
import os

let signposter = OSSignposter(subsystem: "com.example.app", category: "DataLoad")

func loadData() async {
    let state = signposter.beginInterval("loadData")
    let result = await fetchFromNetwork()
    signposter.endInterval("loadData", state)
    process(result)
}
Time Profiler の使用
  1. Product > Profile(Cmd+I)を選択して Instruments を起動します。
  2. Time Profiler テンプレートを選択します。
  3. 動作が遅い操作を再現しながら記録します。
  4. メインスレッドに注目します。「Weight」で並べ替えて、負荷の高いコードパスを見つけます。
  5. 「Hide System Libraries」をオンにして、自分のコードだけを表示します。
  6. 負荷の高いフレームをダブルクリックすると、ソースコードに移動できます。
ハングの一般的な原因
原因症状対処法
メインスレッドでの同期 I/Oネットワーク/ファイルの読み込みで UI がブロックされるTask { } またはバックグラウンドアクターに移動する
ロック競合バックグラウンド処理が保持するロックをメインスレッドが待機するアクターを使用するか、ロックの適用範囲を小さくする
レイアウトの頻繁な再計算layoutSubviews が繰り返し呼び出されるレイアウトの変更をまとめ、強制的なレイアウトを避ける
大きなペイロードの JSON 解析データ読み込み中に UI がフリーズするバックグラウンドスレッドで解析する
同期的な画像デコード画像の多いリストでスクロールがカクつくAsyncImage を使用するか、メインスレッド以外でデコードする

ビルド失敗のトリアージ

コンパイラ診断の読み方
  • 最初のエラーから確認します。後続のエラーは、最初のエラーに連鎖していることがよくあります。
  • ビルドログでエラーコード(例:error: cannot convert)を検索します。
  • Report Navigator(Cmd+9)を使うと、タイムスタンプ付きのビルドログ全体を確認できます。
SPM の依存関係の解決
# Common: version conflict
error: Dependencies could not be resolved because root depends on 'Package' 1.0.0..<2.0.0

# Fix: check Package.resolved and update version ranges
# Reset package caches if needed:
rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf .build
swift package resolve
モジュールが見つからない/リンカーエラー
エラー確認項目
No such module 'Foo'ターゲットメンバーシップ、インポートパス、フレームワーク検索パス
Undefined symbolリンクフェーズでのフレームワークの指定漏れ、アーキテクチャの誤り
duplicate symbol2 つのターゲットが同じシンボルを定義していないか。Objective-C の命名衝突を確認

最初に確認するビルド設定:

  • FRAMEWORK_SEARCH_PATHS
  • OTHER_LDFLAGS
  • SWIFT_INCLUDE_PATHS
  • BUILD_LIBRARY_FOR_DISTRIBUTION(XCFrameworks 用)

Instruments の概要

テンプレートの選び方
テンプレート使用する状況
Time ProfilerCPU 使用率が高い、UI の動作が遅い、負荷の高いコードパスを見つけたい場合
Allocations時間の経過とともにメモリ使用量が増える、オブジェクトのライフタイムを追跡したい場合
Leaksretain cycle や解放されていないオブジェクトが疑われる場合
NetworkHTTP リクエスト/レスポンスのタイミングとペイロードを調べる場合
SwiftUIビューの body 評価と更新頻度をプロファイリングする場合
Animation Hitches / Core Animation instrumentsフレーム落ち、カクつき、ブレンディング、コミット/レンダリング処理を調べる場合
Power Profilerバッテリー消費、熱負荷、バックグラウンドでの電力への影響を調べる場合
File Activityディスク I/O が過剰、ファイル操作が遅い場合
System Traceスレッドのスケジューリング、システムコール、仮想メモリのページフォールトを調べる場合
CI プロファイリング用の xctrace CLI
# Record a trace from the command line
xcrun xctrace record --device "My iPhone" \
    --template "Time Profiler" \
    --instrument "Allocations" \
    --output profile.trace \
    --launch -- /path/to/MyApp.app

# Export trace data as XML for automated analysis
xcrun xctrace export --input profile.trace --xpath '/trace-toc/run/data/table'

# List available templates
xcrun xctrace list templates

# List connected devices
xcrun xctrace list devices

記録ごとに --template は 1 つ指定します。追加のインスツルメントは --instrument で指定します。CI パイプラインで xctrace を使うと、パフォーマンスのリグレッションを 自動的に検出できます。ビルド間でエクスポートしたメトリクスを比較します。

よくある間違い

やってはいけないこと:デバッグに os.Logger ではなく print() を使う

Logger を使うと、ログレベル、プライバシーメタデータ、サブシステム/カテゴリによるフィルタリングを利用できます。 .debug はメモリには保持されますが、リリースビルドでは永続化されません。

// WRONG — unstructured and not filterable by subsystem/category
print("user tapped button, state: \(viewModel.state)")
print("network response: \(data)")

// CORRECT — structured logging with Logger
import os

let logger = Logger(subsystem: "com.example.app", category: "UI")

logger.debug("Button tapped, state: \(viewModel.state, privacy: .public)")
logger.info("Network response received, bytes: \(data.count)")
やってはいけないこと:メモリのデバッグ前に Malloc Stack Logging を有効にし忘れる
// WRONG — open Memory Graph without enabling Malloc Stack Logging
// Result: leaked objects visible but no allocation backtrace

// CORRECT — enable BEFORE running:
// Scheme > Run > Diagnostics > check "Malloc Stack Logging: All Allocations"
// Then run, reproduce the leak, and open Memory Graph
やってはいけないこと:最適化されたコードで、すべての変数を確認できると思ってデバッグする
// WRONG — profiling with Debug build, debugging with Release build
// Debug builds: extra runtime checks distort perf measurements
// Release builds: variables show as "<optimized out>" in debugger

// CORRECT approach:
// Debugging: use Debug configuration (full symbols, no optimization)
// Profiling: use Release configuration (realistic performance)
やってはいけないこと:条件付きブレークポイントを使わずに、ループのすべての反復で停止する
// WRONG — breakpoint on line inside loop, stops 10,000 times
for item in items {
    process(item)  // breakpoint here stops on EVERY item
}

// CORRECT — use a conditional breakpoint:
// (lldb) br set -f MyFile.swift -l 42 -c "item.id == targetID"
// Or in Xcode: right-click breakpoint > Edit > add Condition
やってはいけないこと:Thread Sanitizer の警告を無視する

Thread Sanitizer(TSan)の警告は、断続的にしかクラッシュしない可能性のあるデータ競合を示しています。ツールの問題だと切り分けられていない限り、実際のバグとして扱ってください。

// WRONG — ignoring TSan warning about concurrent access
var cache: [String: Data] = [:]  // accessed from multiple threads

// CORRECT — protect shared mutable state
actor CacheActor {
    var cache: [String: Data] = [:]

    func get(_ key: String) -> Data? { cache[key] }
    func set(_ key: String, _ value: Data) { cache[key] = value }
}

TSan を有効にするには、Scheme > Run > Diagnostics > Thread Sanitizer の順に選択します。iOS、iPadOS、 tvOS、visionOS、watchOS のアプリでは、Simulator 上で TSan を実行してください。Apple のドキュメントでは、デバイスのサポートは 64 ビット macOS アプリのみとされています。

レビューチェックリスト

  • 診断出力には print() ではなく os.Logger を使用する
  • メモリデバッグセッションの前に Malloc Stack Logging を有効にする
  • dismiss/dealloc フローの後に Memory Graph Debugger を確認する
  • 保持サイクルを防ぐため、デリゲートを weak var として宣言する
  • プロパティとして保持するクロージャでは、キャプチャリストに [weak self] を使用する
  • タイマーでは [weak self] を使用したクロージャベースの API を使う
  • 競合の切り分けに備え、Simulator のテストスキームで Thread Sanitizer を有効にする
  • メインスレッドで同期 I/O や負荷の高い計算を行わない
  • パフォーマンスの基準値を把握するため、Release ビルドで Time Profiler を実行する
  • ビルド失敗は、ビルドログの最初のエラーから切り分ける
  • カスタムのパフォーマンス計測区間には OSSignposter を使用する
  • ループやコレクションのデバッグには条件付きブレークポイントを使用する

参考資料

コメント