axiom-swiftui-debugging-diag について
複雑な SwiftUI のビュー更新、アイデンティティ、パフォーマンス、プレビューの不具合を調べる診断スキルです。一時的なログと Instruments の調査を組み合わせ、根本原因を隠す修正を避けます。
主な機能
- 01Self._printChanges() の分析
- 02SwiftUI Instrument による追跡
- 03ビューアイデンティティの調査
- 04環境依存関係の確認
- 05プレビュー診断
活用シーン
- 01ビューが予期せず、または頻繁に更新される場合
- 02状態がリセットされる、またはアニメーションが失敗する場合
- 03複数のビューにまたがるデータ変化を追跡する場合
- 04明確なエラーなしでプレビューがクラッシュする場合
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-swiftui-debugging-diagインストール後、Claude Code で/axiom-swiftui-debugging-diagを入力して読み込まれたか確認します。
詳細
SwiftUI デバッグ診断
この診断スキルを使用するタイミング
次の場合にこのスキルを使用してください:
- 基本的なトラブルシューティングで解決しない —
axiom-swiftui-debuggingスキルのパターンを適用しても、問題が解決しない - Self._printChanges() で予期しないパターンが表示される — 更新されるべきでないビューが更新される、または更新されるべきビューが更新されない
- 断続的な問題 — 動作するときもあれば、失敗するときもある(「ハイゼンバグ」)
- 複雑な依存関係の連鎖 — 複数のビューやモデルを通じたデータフローを追跡する必要がある
- パフォーマンス調査 — ビューの更新頻度が高すぎる、または更新に時間がかかりすぎる
- プレビューで原因不明の問題 — すぐには原因がわからないクラッシュや失敗
禁止事項
切迫した状況では、問題を診断するのではなく、隠すための近道を試したくなるでしょう。次のことは絶対にしないでください:
❌ ランダムに @State/@Observable を変更して推測する
- 「ここに @Observable を追加して、動くか試してみましょう」
- 「これを @StateObject に変更すれば直るかもしれません」
❌ 更新を強制するために .id(UUID()) を追加する
- レンダリングのたびに新しいビュー ID が作成されます
- 状態の保持が失われます
- 根本原因が隠されます
❌ @Observable で対応できるのに ObservableObject を使う(iOS 17+)
- 不必要な複雑さが加わります
- 自動的な依存関係追跡のメリットを逃します
❌ 断続的な問題を無視する(「ときどき動く」)
- 「とりあえずマージして、本番環境で起きないことを願いましょう」
- 断続的な問題は、ランダムではなく体系的なバグです
❌ 理解しないままリリースする
- 「修正は動いていますが、なぜかはわかりません」
- 本番環境で試行錯誤するにはコストがかかりすぎます
必須の最初の手順
診断パターンに取り組む前に、環境のベースラインを確認します:
# 1. Verify Instruments setup
xcodebuild -version # Must be Xcode 26+ for SwiftUI Instrument
# 2. Build in Release mode for profiling
xcodebuild build -scheme YourScheme -configuration Release
# 3. Clear derived data if investigating preview issues
rm -rf ~/Library/Developer/Xcode/DerivedData
所要時間:5 分
理由:Xcode のバージョンや Debug モードが正しくないと、プロファイリングデータが誤解を招くためです
診断の判断ツリー
SwiftUI view issue after basic troubleshooting?
│
├─ View not updating?
│ ├─ Basic check: Add Self._printChanges() temporarily
│ │ ├─ Shows "@self changed" → View value changed
│ │ │ └─ Pattern D1: Analyze what caused view recreation
│ │ ├─ Shows specific state property → That state triggered update
│ │ │ └─ Verify: Should that state trigger update?
│ │ └─ Nothing logged → Body not being called at all
│ │ └─ Pattern D3: View Identity Investigation
│ └─ Advanced: Use SwiftUI Instrument
│ └─ Pattern D2: SwiftUI Instrument Investigation
│
├─ View updating too often?
│ ├─ Pattern D1: Self._printChanges() Analysis
│ │ └─ Identify unnecessary state dependencies
│ └─ Pattern D2: SwiftUI Instrument → Cause & Effect Graph
│ └─ Trace data flow, find broad dependencies
│
├─ Intermittent issues (works sometimes)?
│ ├─ Pattern D3: View Identity Investigation
│ │ └─ Check: Does identity change unexpectedly?
│ ├─ Pattern D4: Environment Dependency Check
│ │ └─ Check: Environment values changing frequently?
│ └─ Reproduce in preview 30+ times
│ └─ If can't reproduce: Likely timing/race condition
│
└─ Preview crashes (after basic fixes)?
├─ Pattern D5: Preview Diagnostics (Xcode 26)
│ └─ Check diagnostics button, crash logs
└─ If still fails: Pattern D2 (profile preview build)
診断パターン
パターン D1:Self._printChanges() の分析
所要時間:5 分
症状:ビューの body が実行される正確な理由を理解したい
使用する場面:
- ビューが予想より頻繁に更新される
- ビューが更新されるべきときに更新されない
- リファクタリング後に依存関係を確認する
手法:
struct MyView: View {
@State private var count = 0
@Environment(AppModel.self) private var model
var body: some View {
let _ = Self._printChanges() // Add temporarily
VStack {
Text("Count: \(count)")
Text("Model value: \(model.value)")
}
}
}
出力の解釈:
# Scenario 1: View parameter changed
MyView: @self changed
→ Parent passed new MyView instance
→ Check parent code - what triggered recreation?
# Scenario 2: State property changed
MyView: count changed
→ Local @State triggered update
→ Expected if you modified count
# Scenario 3: Environment property changed
MyView: @self changed # Environment is part of @self
→ Environment value changed (color scheme, locale, custom value)
→ Pattern D4: Check environment dependencies
# Scenario 4: Nothing logged
→ Body not being called
→ Pattern D3: View identity investigation
よくある発見:
-
予期していないのに「@self changed」と表示される
- 親ビューが不要にビューを再作成している
- 親ビューの状態管理を確認する
-
値を変更していないのに、プロパティが変更されたと表示される
- 間接的な依存関係(変更されたオブジェクトから読み取っている)
- パターン D2:Instruments を使って追跡する
-
複数のプロパティが同時に変更される
- 広範な依存関係(例:1 つの項目だけが必要なのに配列全体を読み取っている)
- 対処法:必要な依存関係だけを取り出す
確認:
- コミットする前に
Self._printChanges()の呼び出しを削除する - このコードを含めたまま本番環境にリリースしない
関連項目:複雑なケースでは、パターン D2(SwiftUI Instrument)を使用します
パターン D2:SwiftUI Instrument による調査
所要時間:25 分
症状:Self._printChanges() だけでは完全に説明できない複雑な更新パターン
使用する場面:
- 1 つだけ更新されるべきなのに複数のビューが更新される
- アプリ内のデータフローを追跡する必要がある
- ビューが更新されるものの、どのデータがきっかけなのかわからない
- ビューの body の更新に時間がかかる(パフォーマンスの問題)
前提条件:
- Xcode 26+ がインストールされている
- デバイスが iOS 26+ / macOS Tahoe+ にアップデートされている
- Release モードでビルドする
手順:
1. Instruments を起動する(5 分)
# Build Release
xcodebuild build -scheme YourScheme -configuration Release
# Launch Instruments
# Press Command-I in Xcode
# Choose "SwiftUI" template
2. トレースを記録する(3 分)
- Record ボタンをクリックする
- 予期しない更新を引き起こす操作を実行する
- 記録を停止する(10〜30 秒の操作で十分です)
3. 時間のかかるビューの body 更新を分析する(5 分)
- Long View Body Updates lane を確認する
- オレンジ色や赤色のバーがある場合、それらは処理コストの高いビューです
- 時間のかかる更新をクリックすると、Detail ペインにビュー名が表示されます
- 右クリックして「Set Inspection Range and Zoom」を選択する
- Time Profiler トラックに切り替える
- コールスタックで対象のビューを見つける
- コストの高い処理(フォーマッターの作成、計算など)を特定する
対処法:コストの高い処理をモデルレイヤーに移動し、結果をキャッシュする
4. 不要な更新を分析する(7 分)
- ユーザー操作の時間範囲をハイライトします(例:お気に入りボタンのタップ)
- 詳細ペインで階層を展開します
- 更新回数を数えます — 想定より多くありませんか?
- ビューにカーソルを合わせる → 矢印をクリック → 「Show Cause & Effect Graph」
5. Cause & Effect Graph を解釈する(5 分)
グラフのノード:
[Blue node] = Your code (gesture, state change, view body)
[System node] = SwiftUI/system work
[Arrow labeled "update"] = Caused this update
[Arrow labeled "creation"] = Caused view to appear
よくあるパターン:
# Pattern A: Single view updates (GOOD)
[Gesture] → [State Change in ViewModelA] → [ViewA body]
# Pattern B: All views update (BAD - broad dependency)
[Gesture] → [Array change] → [All list item views update]
└─ Fix: Use granular view models, one per item
# Pattern C: Cascade through environment (CHECK)
[State Change] → [Environment write] → [Many view bodies check]
└─ If environment value changes frequently → Pattern D4 fix
ノードをクリックする:
- 状態変更ノード → 値が設定された場所のバックトレースを確認します
- ビュー本体ノード → 読み取られたプロパティ(依存関係)を確認します
検証:
- 修正後の新しいトレースを記録します
- 修正前後の更新回数を比較します
- 赤/オレンジ色のバーが減少または解消したことを確認します
相互参照:詳細な Instruments のワークフローについては、axiom-swiftui-performance スキルを参照してください
パターン D3:ビューの識別情報の調査
所要時間:15 分
症状:@State の値が予期せずリセットされる、またはビューがアニメーションしない
使用する状況:
- リセットされるべきでないのに、カウンターが 0 にリセットされる
- アニメーションが機能しない(アニメーションせずにビューが切り替わる)
- ForEach の項目が飛び回る
- テキストフィールドのフォーカスが失われる
根本原因:ビューの識別情報が予期せず変化しています
調査手順:
1. 条件分岐内への配置を確認する(5 分)
// ❌ PROBLEM: Identity changes with condition
if showDetails {
CounterView() // Gets new identity each time showDetails toggles
}
// ✅ FIX: Use .opacity()
CounterView()
.opacity(showDetails ? 1 : 0) // Same identity always
確認方法:コードベースを検索し、状態を保持するビューが if/else の内側にないか確認します
2. .id() 修飾子を確認する(5 分)
// ❌ PROBLEM: .id() changes when data changes
DetailView()
.id(item.id + "-\(isEditing)") // ID changes with isEditing
// ✅ FIX: Stable ID
DetailView()
.id(item.id) // Stable ID
確認方法:コードベースで .id( を検索し、ID の値が変化していないか確認します
3. ForEach の識別子を確認する(5 分)
// ❌ WRONG: Index-based ID
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
Text(item.name)
}
// ❌ WRONG: Non-unique ID
ForEach(items, id: \.category) { item in // Multiple items per category
Text(item.name)
}
// ✅ RIGHT: Unique, stable ID
ForEach(items, id: \.id) { item in
Text(item.name)
}
確認方法:ForEach を検索し、一意で安定した ID か確認します
修正パターン:
| 問題 | 修正 |
|---|---|
| 条件分岐内のビュー | 代わりに .opacity() を使用します |
| .id() が頻繁に変化する | 安定した識別子を使用します |
| ForEach の項目が飛び回る | 一意で安定した ID(UUID またはサーバー ID)を使用します |
| ナビゲーション時に状態がリセットされる | NavigationStack のパス管理を確認します |
検証:
- Self._printChanges() を追加します — 「@self changed」が繰り返し表示されないことを確認します
- アニメーションがスムーズに動作するはずです
- @State の値が保持されるはずです
パターン D4:環境依存関係の確認
所要時間:10 分
症状:無関係なデータが変化したときに、多くのビューが更新される
使用する状況:
- Cause & Effect Graph で「Environment」ノードが多くの更新を引き起こしている
- スクロールやアニメーションのパフォーマンスが低下する
- 予期しない更新が連鎖する
根本原因:環境内の値が頻繁に変化する、または環境を読み取るビューが多すぎます
調査手順:
1. 環境への書き込みを見つける(3 分)
# Search for environment modifiers in current project
grep -r "\.environment(" --include="*.swift" .
次を確認します:
// ❌ BAD: Frequently changing values
.environment(\.scrollOffset, scrollOffset) // Updates 60+ times/second
.environment(model) // If model updates frequently
// ✅ GOOD: Stable values
.environment(\.colorScheme, .dark)
.environment(appModel) // If appModel changes rarely
2. 環境に何が含まれているか確認する(3 分)
パターン D2(Instruments)を使用して、Cause & Effect Graph を確認します:
- 「Environment」ノードをクリックします
- どのプロパティが変化したか確認します
- 更新対象として確認されたビューの数を数えます
確認事項:
- この値は、スクロールやアニメーションのフレームごとに変化していますか?
- これらすべてのビューで、本当にこの値が必要ですか?
3. 修正を適用する(4 分)
修正 A:環境から削除する(頻繁に変化する場合):
// ❌ Before: Environment
.environment(\.scrollOffset, scrollOffset)
// ✅ After: Direct parameter
ChildView(scrollOffset: scrollOffset)
修正 B:@Observable モデルを使用する(多くのビューで必要な場合):
// Instead of storing primitive in environment:
@Observable class ScrollViewModel {
var offset: CGFloat = 0
}
// Views depend on specific properties:
@Environment(ScrollViewModel.self) private var viewModel
var body: some View {
Text("\(viewModel.offset)") // Only updates when offset changes
}
検証:
- Instruments で新しいトレースを記録します
- Cause & Effect Graph を確認します — 更新されるビューが減っているはずです
- パフォーマンスが向上しているはずです(スクロールやアニメーションがよりスムーズになります)
パターン D5:プレビューの診断(Xcode 26)
所要時間:10 分
症状:プレビューが読み込めない、または原因が不明なエラーでクラッシュする
使用する状況:
- 基本的な修正後もプレビューが失敗する(swiftui-debugging スキル)
- エラーメッセージが不明瞭、または一般的すぎる
- 以前は動作していたプレビューが突然動かなくなった
調査手順:
1. Preview Diagnostics ボタンを使用する(2 分)
場所:エディタメニュー → Canvas → Diagnostics
表示内容:
- 詳細なエラーメッセージ
- 不足している依存関係
- 状態の初期化に関する問題
- プレビュー固有の問題
2. クラッシュログを確認する(3 分)
# Open crash logs directory
open ~/Library/Logs/DiagnosticReports/
# Look for recent .crash files containing "Preview"
ls -lt ~/Library/Logs/DiagnosticReports/ | grep -i preview | head -5
確認する内容:
- 致命的なエラー(配列の範囲外アクセス、nil の強制アンラップ)
- モジュールのインポート漏れ
- フレームワークの初期化失敗
3. 問題を切り分ける(5 分)
最小限のプレビューを作成:
// Start with empty preview
#Preview {
Text("Test")
}
// If this works, gradually add:
#Preview {
MyView() // Your actual view, but with mock data
.environment(MockModel()) // Provide all dependencies
}
// Find which dependency causes crash
よくある問題:
| エラー | 原因 | 修正方法 |
|---|---|---|
| "Cannot find in scope" | 依存関係が不足している | プレビューに追加します(下の例を参照) |
| "Fatal error: Unexpectedly found nil" | オプショナルのアンラップに失敗した | プレビューに nil ではない値を指定します |
| "No such module" | インポートが不足している | import 文を追加します |
| エラーのないクラッシュ | 無効な値で State を初期化している | 安全なデフォルト値を使用します |
修正パターン:
// Missing @Environment
#Preview {
ContentView()
.environment(AppModel()) // Provide dependency
}
// Missing @EnvironmentObject (pre-iOS 17)
#Preview {
ContentView()
.environmentObject(AppModel())
}
// Missing ModelContainer (SwiftData)
#Preview {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try! ModelContainer(for: Item.self, configurations: config)
return ContentView()
.modelContainer(container)
}
// State with invalid defaults
@State var selectedIndex = 10 // ❌ Out of bounds
let items = ["a", "b", "c"]
// Fix: Safe default
@State var selectedIndex = 0 // ✅ Valid index
確認:
- プレビューがエラーなく読み込まれる
- プレビューを通常どおり操作できる
- 変更がすぐに反映される
本番環境の危機的状況
状況
背景:
- iOS 26 のビルドを 2 日前にリリース
- ユーザーから「機能の切り替え時に設定画面がフリーズする」と報告がある
- ユーザーの 15% が影響を受けている(App Store のレビューで報告)
- VP から 2 時間ごとに進捗報告を求められている
- 次のデプロイ可能時間帯が終了するまで 8 時間
- ジュニアエンジニアの提案:「@ObservedObject への切り替えを試してみます」
危険信号 — これらには抵抗する
締め切りのプレッシャーの下で以下のどれかを耳にしたら、手を止めて診断パターンを使用してください:
❌ 「別のプロパティラッパーを試して、何がうまくいくか見てみます」
- むやみな変更は当てずっぽうです
- 悪化させる可能性は 80% です
❌ 「自分のデバイスでは動くので、iOS 26 のバグに違いありません」
- ユーザーからの報告は事実です
- 15% という割合は、系統的な問題であり、まれなケースではありません
❌ 「修正がうまくいかなければロールバックできます」
- App Store の審査には 24 時間かかります
- ロールバックはすぐにはできません
❌ 「強制的に更新するために .id(UUID()) を追加しましょう」
- 状態の保持が損なわれます
- 根本原因が隠れてしまいます
❌ 「ひとまずパフォーマンスが低下しても、ユーザーは受け入れてくれます」
- リリースしたら、24 時間は対応を変えられません
- 悪いレビューは残り続けます
必須の手順(近道なし)
合計時間の目安:90 分
フェーズ 1:再現(15 分)
# 1. Get exact steps from user report
# 2. Build Release mode
xcodebuild build -scheme YourApp -configuration Release
# 3. Test on device (not simulator)
# 4. Reproduce freeze 3+ times
再現できない場合:影響を受けたユーザーに動画の録画またはデバイスログを依頼します
フェーズ 2:パターン D2 で診断(30 分)
# Launch Instruments with SwiftUI template
# Command-I in Xcode
# Record while reproducing freeze
# Look for:
# - Long View Body Updates (red bars)
# - Cause & Effect Graph showing update cascade
特定する項目:
- どのビューのコストが高いか?
- どのデータ変更が引き金になったか?
- 何個のビューが更新されたか?
フェーズ 3:対象を絞った修正を適用(20 分)
診断結果に基づきます:
ビュー本体の更新に時間がかかる場合:
// Example finding: Formatter creation in body
// Fix: Move to cached formatter
更新の連鎖が発生している場合:
// Example finding: All toggle views reading entire settings array
// Fix: Per-toggle view models with granular dependencies
Environment に問題がある場合:
// Example finding: Environment value updating every frame
// Fix: Remove from environment, use direct parameter
フェーズ 4:検証(15 分)
# Record new Instruments trace
# Compare before/after:
# - Long updates eliminated?
# - Update count reduced?
# - Freeze gone?
# Test on device 10+ times
フェーズ 5:根拠を添えてデプロイ(10 分)
Slack to VP + team:
"Diagnostic complete: Settings screen freeze caused by formatter creation
in ToggleRow body (confirmed via SwiftUI Instrument, Long View Body Updates).
Each toggle tap recreated NumberFormatter + DateFormatter for all visible
toggles (20+ formatters per tap).
Fix: Cached formatters in SettingsViewModel, pre-formatted strings.
Verified: Settings screen now responds in <16ms (was 200ms+).
Deploying build 2.1.1 now. Will monitor for next 24 hours."
これにより、以下を示せます:
- 推測ではなく、根拠に基づいて診断した
- 根本原因を理解している
- 修正を検証した
- 確信を持ってリリースしている
所要時間の比較
選択肢 A:推測して祈る
- むやみな修正を試す時間:30 分
- デプロイにかかる時間:20 分
- 失敗したとわかるまでの時間:24 時間(次回の App Store 審査)
- 合計の遅延:24 時間以上
- ユーザーの被害:デプロイ可能時間帯の間も続く
- リスク:問題を悪化させ、バグが 2 つになる
選択肢 B:診断手順(このスキル)
- 診断にかかる時間:45 分
- 対象を絞った修正の適用にかかる時間:20 分
- 検証にかかる時間:15 分
- デプロイにかかる時間:10 分
- 合計時間:90 分
- ユーザーの被害:2 時間後に解消
- 確信度:高い(根拠に基づく修正)
削減できる時間:22 時間。さらに、問題の悪化も回避できます
プレッシャーが妥当な場合
マネージャーが速度を求めるのが妥当な場合もあります。以下の条件を満たすなら、そのプレッシャーを受け入れましょう。
✅ 診断手順を完了している(90 分) ✅ 問題の原因となっているビューや操作を正確に把握している ✅ 推測ではなく、対象を絞った修正を用意している ✅ リリース前に Instruments で検証している ✅ 願うだけではなく、根拠を添えてリリースしている
決定内容を記録します(上記と同じ Slack テンプレート)
反論する際に使えるプロフェッショナルな文面
診断を省略するよう求められた場合:
「緊急性は理解しています。診断を省略すると、誤った修正をリリースする可能性が 80% あり、ユーザーがさらに 24 時間苦しむことになります。診断手順は合計 90 分で、根拠に基づいた確信が得られます。悪化させるリスクなく、検証済みの修正を 2 時間以内にデプロイできます。計算上、診断が解決への最短ルートです。」
クイックリファレンステーブル
| 症状 | 考えられる原因 | 最初に確認する項目 | パターン | 修正時間 |
|---|---|---|---|---|
| ビューが更新されない | オブザーバーがない/状態が誤っている | Self._printChanges() | D1 | 10 min |
| ビューが頻繁に更新される | 依存関係が広すぎる | Self._printChanges() → Instruments | D1 → D2 | 30 min |
| 状態がリセットされる | アイデンティティの変更 | .id() 修飾子、条件分岐 | D3 | 15 min |
| 更新が連鎖する | 環境の問題 | 環境修飾子 | D4 | 20 min |
| プレビューがクラッシュする | 依存関係がない/初期化が不適切 | Diagnostics ボタン | D5 | 10 min |
| 問題が断続的に発生する | アイデンティティまたはタイミング | 30 回以上再現する | D3 | 30 min |
| 更新に時間がかかる(パフォーマンス) | body 内の処理コストが高い | Instruments(SwiftUI + Time Profiler) | D2 | 30 min |
判断フレームワーク
修正をリリースする前に、必ず次の点を確認してください。
| 質問 | 「はい」ですか? | 対応 |
|---|---|---|
| Self._printChanges() を使いましたか? | いいえ | STOP - パターン D1(5 分) |
| SwiftUI Instrument を実行しましたか? | いいえ | STOP - パターン D2(25 分) |
| 問題の原因を一文で説明できますか? | いいえ | STOP - 推測しています |
| Instruments で修正を検証しましたか? | いいえ | STOP - リリース前にテストしてください |
| より単純な説明がないか確認しましたか? | いいえ | STOP - 診断パターンを見直してください |
5 つすべてに「はい」と答えたら → 自信を持ってリリースできます
よくある間違い
間違い 1:「@Observable を追加したら直りました」
なぜ間違いなのか:なぜ直ったのかわかっていません
- 今は動いても、後で壊れる可能性があります
- 別のバグを隠してしまった可能性があります
正しい方法:
- パターン D1(Self._printChanges())を使って、変更前の状態を確認します
- @Observable を適用します
- もう一度パターン D1 を使って、変更後の状態を確認します
- 何が変わったのかを正確に把握します
間違い 2:「簡単な修正に Instruments を使うのは時間がかかりすぎます」
なぜ間違いなのか:推測が外れた場合は、かえって時間がかかります
- 診断に 25 分かければ、確実に修正できます
- 5 分の推測を 3 回試すと、合計 15 分かかるうえに、問題は解決しないままです
正しい方法:
- 本番環境の問題は必ずプロファイルします
- 単純なケースでは Self._printChanges() を使います
間違い 3:「修正は動いているので、検証する必要はありません」
なぜ間違いなのか:手動テストは検証とは異なります
- テストした特定のケースでは動くかもしれません
- エッジケースでは失敗するかもしれません
- パフォーマンスのリグレッションを引き起こしたかもしれません
正しい方法:
- 修正後は必ず Instruments で検証します
- 修正前後のトレースを比較します
- エッジケース(空のデータ、大量のデータなど)をテストします
コマンドのクイックリファレンス
Instruments コマンド
# Launch Instruments with SwiftUI template
# 1. In Xcode: Command-I
# 2. Or from command line:
open -a Instruments
# Build in Release mode (required for accurate profiling)
xcodebuild build -scheme YourScheme -configuration Release
# Clean derived data if needed
rm -rf ~/Library/Developer/Xcode/DerivedData
Self._printChanges() のデバッグパターン
// Add temporarily to view body
var body: some View {
let _ = Self._printChanges() // Shows update reason
// Your view code
}
忘れずに:コミット前に削除してください!
プレビューの診断
# Check preview crash logs
open ~/Library/Logs/DiagnosticReports/
# Filter for recent preview crashes
ls -lt ~/Library/Logs/DiagnosticReports/ | grep -i preview | head -5
# Xcode menu path:
# Editor → Canvas → Diagnostics
環境の検索
# Find environment modifiers
grep -r "\.environment(" --include="*.swift" .
# Find environment object usage
grep -r "@Environment" --include="*.swift" .
# Find view identity modifiers
grep -r "\.id(" --include="*.swift" .
Instruments の操作
Instruments で(記録後):
- SwiftUI トラックを選択します
- 展開して、次の項目を表示します:
- Update Groups レーン
- Long View Body Updates レーン
- Long Representable Updates レーン
- Long View Body Updates の概要をクリックします
- 更新を右クリックし、「Set Inspection Range and Zoom」を選択します
- Time Profiler トラックに切り替えます
- コールスタックでビューを探します(Command-F)
原因と結果のグラフ:
- 詳細ペインで階層を展開します
- ビュー名にカーソルを合わせ、矢印をクリックします
- 「Show Cause & Effect Graph」を選択します
- ノードをクリックすると、次の情報が表示されます:
- 状態変更ノード → バックトレース
- ビューの body ノード → 依存関係
リソース
WWDC:2025-306、2023-10160、2023-10149、2021-10022
ドキュメント:/xcode/understanding-hitches-in-your-app、/xcode/analyzing-hangs-in-your-app、/swiftui/managing-model-data-in-your-app
スキル:axiom-swiftui-debugging、axiom-swiftui-performance、axiom-swiftui-layout、axiom-xcode-debugging