axiom-swiftui-debugging-diag
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftUI Debugging Diagnostics
SwiftUI 调试诊断
When to Use This Diagnostic Skill
何时使用此诊断技能
Use this skill when:
- Basic troubleshooting failed — Applied skill patterns but issue persists
axiom-swiftui-debugging - Self._printChanges() shows unexpected patterns — View updating when it shouldn't, or not updating when it should
- Intermittent issues — Works sometimes, fails other times ("heisenbug")
- Complex dependency chains — Need to trace data flow through multiple views/models
- Performance investigation — Views updating too often or taking too long
- Preview mysteries — Crashes or failures that aren't immediately obvious
在以下场景使用此技能:
- 基础排查无效 — 已应用技能模式但问题仍存在
axiom-swiftui-debugging - Self._printChanges()显示意外模式 — 视图在不应更新时更新,或应更新时未更新
- 间歇性问题 — 有时正常工作,有时失败(“海森堡bug”)
- 复杂依赖链 — 需要追踪多视图/模型间的数据流
- 性能排查 — 视图更新过于频繁或耗时过长
- 预览异常 — 崩溃或故障原因不明显
FORBIDDEN Actions
禁止操作
Under pressure, you'll be tempted to shortcuts that hide problems instead of diagnosing them. NEVER do these:
❌ Guessing with random @State/@Observable changes
- "Let me try adding @Observable here and see if it works"
- "Maybe if I change this to @StateObject it'll fix it"
❌ Adding .id(UUID()) to force updates
- Creates new view identity every render
- Destroys state preservation
- Masks root cause
❌ Using ObservableObject when @Observable would work (iOS 17+)
- Adds unnecessary complexity
- Miss out on automatic dependency tracking
❌ Ignoring intermittent issues ("works sometimes")
- "I'll just merge and hope it doesn't happen in production"
- Intermittent = systematic bug, not randomness
❌ Shipping without understanding
- "The fix works, I don't know why"
- Production is too expensive for trial-and-error
在压力下,你可能会想走捷径掩盖问题而非诊断问题。绝对不要做以下操作:
❌ 随意修改@State/@Observable进行猜测
- “我试试在这里加个@Observable看看能不能解决”
- “也许把这个改成@StateObject就好了”
❌ 添加.id(UUID())强制更新
- 每次渲染都会创建新的视图标识
- 破坏状态保留
- 掩盖根本原因
❌ 在iOS 17+环境下使用ObservableObject而非@Observable
- 增加不必要的复杂度
- 无法利用自动依赖追踪
❌ 忽略间歇性问题(“有时能正常工作”)
- “我直接合并代码,希望生产环境不会出现”
- 间歇性问题是系统性bug,而非随机故障
❌ 未理解问题就发布代码
- “修复有效就行,我不管为什么”
- 生产环境不允许试错
Mandatory First Steps
强制初始步骤
Before diving into diagnostic patterns, establish baseline environment:
bash
undefined在开始诊断模式前,先建立基准环境:
bash
undefined1. Verify Instruments setup
1. 验证Instruments设置
xcodebuild -version # Must be Xcode 26+ for SwiftUI Instrument
xcodebuild -version # 必须是Xcode 26+才能使用SwiftUI Instrument
2. Build in Release mode for profiling
2. 以Release模式构建用于性能分析
xcodebuild build -scheme YourScheme -configuration Release
xcodebuild build -scheme YourScheme -configuration Release
3. Clear derived data if investigating preview issues
3. 若排查预览问题,清除派生数据
rm -rf ~/Library/Developer/Xcode/DerivedData
**Time cost**: 5 minutes
**Why**: Wrong Xcode version or Debug mode produces misleading profiling data
---rm -rf ~/Library/Developer/Xcode/DerivedData
**时间成本**:5分钟
**原因**:错误的Xcode版本或Debug模式会产生误导性的性能分析数据
---Diagnostic Decision Tree
诊断决策树
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)基础排查后仍存在SwiftUI视图问题?
│
├─ 视图不更新?
│ ├─ 基础检查:临时添加Self._printChanges()
│ │ ├─ 显示"@self changed" → 视图值已变更
│ │ │ └─ 模式D1:分析视图重建的原因
│ │ ├─ 显示特定状态属性 → 该状态触发了更新
│ │ │ └─ 验证:该状态是否应该触发更新?
│ │ └─ 无日志输出 → 视图体完全未被调用
│ │ └─ 模式D3:视图标识排查
│ └─ 进阶:使用SwiftUI Instrument
│ └─ 模式D2:SwiftUI Instrument排查
│
├─ 视图更新过于频繁?
│ ├─ 模式D1:Self._printChanges()分析
│ │ └─ 识别不必要的状态依赖
│ └─ 模式D2:SwiftUI Instrument → 因果图
│ └─ 追踪数据流,找到宽泛依赖
│
├─ 间歇性问题(有时正常工作)?
│ ├─ 模式D3:视图标识排查
│ │ └─ 检查:标识是否意外变更?
│ ├─ 模式D4:环境依赖检查
│ │ └─ 检查:环境值是否频繁变更?
│ └─ 在预览中重现30次以上
│ └─ 若无法重现:可能是时序/竞态条件问题
│
└─ 预览崩溃(基础修复后)?
├─ 模式D5:预览诊断(Xcode 26)
│ └─ 检查诊断按钮、崩溃日志
└─ 若仍失败:使用模式D2(分析预览构建)Diagnostic Patterns
诊断模式
Pattern D1: Self._printChanges() Analysis
模式D1:Self._printChanges() 分析
Time cost: 5 minutes
Symptom: Need to understand exactly why view body runs
When to use:
- View updating more often than expected
- View not updating when it should
- Verifying dependencies after refactoring
Technique:
swift
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)")
}
}
}Output interpretation:
undefined时间成本:5分钟
症状:需要明确了解视图体执行的原因
适用场景:
- 视图更新频率超出预期
- 视图在应更新时未更新
- 重构后验证依赖关系
方法:
swift
struct MyView: View {
@State private var count = 0
@Environment(AppModel.self) private var model
var body: some View {
let _ = Self._printChanges() // 临时添加
VStack {
Text("Count: \(count)")
Text("Model value: \(model.value)")
}
}
}输出解读:
undefinedScenario 1: View parameter changed
场景1:视图参数变更
MyView: @self changed
→ Parent passed new MyView instance
→ Check parent code - what triggered recreation?
MyView: @self changed
→ 父视图传递了新的MyView实例
→ 检查父视图代码 - 是什么触发了重建?
Scenario 2: State property changed
场景2:状态属性变更
MyView: count changed
→ Local @State triggered update
→ Expected if you modified count
MyView: count changed
→ 本地@State触发了更新
→ 若你修改了count则属于预期情况
Scenario 3: Environment property changed
场景3:环境属性变更
MyView: @self changed # Environment is part of @self
→ Environment value changed (color scheme, locale, custom value)
→ Pattern D4: Check environment dependencies
MyView: @self changed # 环境属于@self的一部分
→ 环境值已变更(配色方案、区域设置、自定义值)
→ 模式D4:检查环境依赖
Scenario 4: Nothing logged
场景4:无日志输出
→ Body not being called
→ Pattern D3: View identity investigation
**Common discoveries**:
1. **"@self changed" when you don't expect**
- Parent recreating view unnecessarily
- Check parent's state management
2. **Property shows changed but you didn't change it**
- Indirect dependency (reading from object that changed)
- Pattern D2: Use Instruments to trace
3. **Multiple properties changing together**
- Broad dependency (e.g., reading entire array when only need one item)
- Fix: Extract specific dependency
**Verification**:
- Remove `Self._printChanges()` call before committing
- Never ship to production with this code
**Cross-reference**: For complex cases, use Pattern D2 (SwiftUI Instrument)
---→ 视图体未被调用
→ 模式D3:视图标识排查
**常见发现**:
1. **意外出现"@self changed"**
- 父视图不必要地重建了当前视图
- 检查父视图的状态管理
2. **属性显示变更但你并未修改它**
- 存在间接依赖(读取了已变更的对象)
- 模式D2:使用Instruments追踪
3. **多个属性同时变更**
- 存在宽泛依赖(例如:仅需单个元素时却读取了整个数组)
- 修复:提取特定依赖
**验证**:
- 提交代码前移除`Self._printChanges()`调用
- 绝对不要将此代码发布到生产环境
**交叉参考**:复杂场景下使用模式D2(SwiftUI Instrument)
---Pattern D2: SwiftUI Instrument Investigation
模式D2:SwiftUI Instrument 排查
Time cost: 25 minutes
Symptom: Complex update patterns that Self._printChanges() can't fully explain
When to use:
- Multiple views updating when one should
- Need to trace data flow through app
- Views updating but don't know which data triggered it
- Long view body updates (performance issue)
Prerequisites:
- Xcode 26+ installed
- Device updated to iOS 26+ / macOS Tahoe+
- Build in Release mode
Steps:
时间成本:25分钟
症状:Self._printChanges()无法完全解释的复杂更新模式
适用场景:
- 一个视图更新导致多个视图同时更新
- 需要追踪应用内的数据流
- 视图更新但不知道触发数据是什么
- 视图体更新耗时过长(性能问题)
前置条件:
- 已安装Xcode 26+
- 设备已更新至iOS 26+ / macOS Tahoe+
- 以Release模式构建
步骤:
1. Launch Instruments (5 min)
1. 启动Instruments(5分钟)
bash
undefinedbash
undefinedBuild Release
构建Release版本
xcodebuild build -scheme YourScheme -configuration Release
xcodebuild build -scheme YourScheme -configuration Release
Launch Instruments
启动Instruments
Press Command-I in Xcode
在Xcode中按Command-I
Choose "SwiftUI" template
选择"SwiftUI"模板
undefinedundefined2. Record Trace (3 min)
2. 记录追踪数据(3分钟)
- Click Record button
- Perform the action that triggers unexpected updates
- Stop recording (10-30 seconds of interaction is enough)
- 点击录制按钮
- 执行触发意外更新的操作
- 停止录制(10-30秒的交互足够)
3. Analyze Long View Body Updates (5 min)
3. 分析耗时视图体更新(5分钟)
- Look at Long View Body Updates lane
- Any orange/red bars? Those are expensive views
- Click on a long update → Detail pane shows view name
- Right-click → "Set Inspection Range and Zoom"
- Switch to Time Profiler track
- Find your view in call stack
- Identify expensive operation (formatter creation, calculation, etc.)
Fix: Move expensive operation to model layer, cache result
- 查看Long View Body Updates轨道
- 存在橙色/红色条?这些是耗时视图
- 点击耗时更新 → 详情面板显示视图名称
- 右键 → "设置检查范围并缩放"
- 切换到Time Profiler轨道
- 在调用栈中找到你的视图
- 识别耗时操作(格式化器创建、计算等)
修复:将耗时操作移至模型层,缓存结果
4. Analyze Unnecessary Updates (7 min)
4. 分析不必要的更新(7分钟)
- Highlight time range of user action (e.g., tapping favorite button)
- Expand hierarchy in detail pane
- Count updates — more than expected?
- Hover over view → Click arrow → "Show Cause & Effect Graph"
- 高亮用户操作的时间范围(例如:点击收藏按钮)
- 在详情面板中展开层级
- 统计更新次数 — 超出预期?
- 悬停在视图上 → 点击箭头 → "显示因果图"
5. Interpret Cause & Effect Graph (5 min)
5. 解读因果图(5分钟)
Graph nodes:
[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 appearCommon patterns:
undefined图节点:
[蓝色节点] = 你的代码(手势、状态变更、视图体)
[系统节点] = SwiftUI/系统工作
[标注"update"的箭头] = 导致此次更新
[标注"creation"的箭头] = 导致视图出现常见模式:
undefinedPattern A: Single view updates (GOOD)
模式A:单个视图更新(良好)
[Gesture] → [State Change in ViewModelA] → [ViewA body]
[Gesture] → [ViewModelA中的状态变更] → [ViewA body]
Pattern B: All views update (BAD - broad dependency)
模式B:所有视图更新(不良 - 宽泛依赖)
[Gesture] → [Array change] → [All list item views update]
└─ Fix: Use granular view models, one per item
[Gesture] → [数组变更] → [所有列表项视图更新]
└─ 修复:使用粒度化视图模型,每个项对应一个
Pattern C: Cascade through environment (CHECK)
模式C:通过环境级联(需检查)
[State Change] → [Environment write] → [Many view bodies check]
└─ If environment value changes frequently → Pattern D4 fix
**Click on nodes**:
- **State change node** → See backtrace of where value was set
- **View body node** → See which properties it read (dependencies)
**Verification**:
- Record new trace after fix
- Compare before/after update counts
- Verify red/orange bars reduced or eliminated
**Cross-reference**: `axiom-swiftui-performance` skill for detailed Instruments workflows
---[状态变更] → [环境写入] → [多个视图体检查]
└─ 若环境值频繁变更 → 应用模式D4修复
**点击节点**:
- **状态变更节点** → 查看值被设置的回溯信息
- **视图体节点** → 查看它读取的属性(依赖项)
**验证**:
- 修复后记录新的追踪数据
- 对比修复前后的更新次数
- 验证红色/橙色条已减少或消除
**交叉参考**:详细Instruments工作流参考`axiom-swiftui-performance`技能
---Pattern D3: View Identity Investigation
模式D3:视图标识排查
Time cost: 15 minutes
Symptom: @State values reset unexpectedly, or views don't animate
When to use:
- Counter resets to 0 when it shouldn't
- Animations don't work (view pops instead of animates)
- ForEach items jump around
- Text field loses focus
Root cause: View identity changed unexpectedly
Investigation steps:
时间成本:15分钟
症状:@State值意外重置,或视图不执行动画
适用场景:
- 计数器在不应重置时归零
- 动画不生效(视图直接出现而非动画过渡)
- ForEach项位置跳跃
- 文本框失去焦点
根本原因:视图标识意外变更
排查步骤:
1. Check for conditional placement (5 min)
1. 检查条件性放置(5分钟)
swift
// ❌ 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 alwaysFind: Search codebase for views inside that hold state
if/elseswift
// ❌ 问题:标识随条件变更
if showDetails {
CounterView() // 每次showDetails切换时都会获取新标识
}
// ✅ 修复:使用.opacity()
CounterView()
.opacity(showDetails ? 1 : 0) // 始终保持相同标识查找方式:搜索代码库中包含状态的内的视图
if/else2. Check .id() modifiers (5 min)
2. 检查.id()修饰符(5分钟)
swift
// ❌ PROBLEM: .id() changes when data changes
DetailView()
.id(item.id + "-\(isEditing)") // ID changes with isEditing
// ✅ FIX: Stable ID
DetailView()
.id(item.id) // Stable IDFind: Search codebase for — check if ID values change
.id(swift
// ❌ 问题:.id()随数据变更
DetailView()
.id(item.id + "-\(isEditing)") // ID随isEditing变更
// ✅ 修复:稳定ID
DetailView()
.id(item.id) // 稳定ID查找方式:搜索代码库中的 — 检查ID值是否会变更
.id(3. Check ForEach identifiers (5 min)
3. 检查ForEach标识符(5分钟)
swift
// ❌ 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)
}Find: Search for — verify unique, stable IDs
ForEachFix patterns:
| Issue | Fix |
|---|---|
| View in conditional | Use |
| .id() changes too often | Use stable identifier |
| ForEach jumping | Use unique, stable IDs (UUID or server ID) |
| State resets on navigation | Check NavigationStack path management |
Verification:
- Add Self._printChanges() — should NOT see "@self changed" repeatedly
- Animations should now work smoothly
- @State values should persist
swift
// ❌ 错误:基于索引的ID
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
Text(item.name)
}
// ❌ 错误:非唯一ID
ForEach(items, id: \.category) { item in // 多个项属于同一分类
Text(item.name)
}
// ✅ 正确:唯一、稳定的ID
ForEach(items, id: \.id) { item in
Text(item.name)
}查找方式:搜索 — 验证使用的是唯一、稳定的ID
ForEach修复模式:
| 问题 | 修复方案 |
|---|---|
| 条件内的视图 | 改用 |
| .id()变更过于频繁 | 使用稳定标识符 |
| ForEach项跳跃 | 使用唯一、稳定的ID(UUID或服务器ID) |
| 导航时状态重置 | 检查NavigationStack路径管理 |
验证:
- 添加Self._printChanges() — 不应反复出现"@self changed"
- 动画现在应流畅执行
- @State值应保持持久
Pattern D4: Environment Dependency Check
模式D4:环境依赖检查
Time cost: 10 minutes
Symptom: Many views updating when unrelated data changes
When to use:
- Cause & Effect Graph shows "Environment" node triggering many updates
- Slow scrolling or animation performance
- Unexpected cascading updates
Root cause: Frequently-changing value in environment OR too many views reading environment
Investigation steps:
时间成本:10分钟
症状:无关数据变更时多个视图同时更新
适用场景:
- 因果图显示"Environment"节点触发了多个更新
- 滚动或动画性能缓慢
- 意外的级联更新
根本原因:环境中存在频繁变更的值,或过多视图读取环境
排查步骤:
1. Find environment writes (3 min)
1. 查找环境写入操作(3分钟)
bash
undefinedbash
undefinedSearch for environment modifiers in current project
在当前项目中搜索环境修饰符
grep -r ".environment(" --include="*.swift" .
**Look for**:
```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 rarelygrep -r ".environment(" --include="*.swift" .
**重点关注**:
```swift
// ❌ 不良:频繁变更的值
.environment(\.scrollOffset, scrollOffset) // 每秒更新60+次
.environment(model) // 若model频繁更新
// ✅ 良好:稳定值
.environment(\.colorScheme, .dark)
.environment(appModel) // 若appModel极少变更2. Check what's in environment (3 min)
2. 检查环境内容(3分钟)
Using Pattern D2 (Instruments), check Cause & Effect Graph:
- Click on "Environment" node
- See which properties changed
- Count how many views checked for updates
Questions:
- Is this value changing every scroll/animation frame?
- Do all these views actually need this value?
使用模式D2(Instruments),查看因果图:
- 点击"Environment"节点
- 查看哪些属性已变更
- 统计有多少视图检查了更新
问题:
- 该值是否在每次滚动/动画帧时都变更?
- 所有这些视图是否真的需要此值?
3. Apply fix (4 min)
3. 应用修复(4分钟)
Fix A: Remove from environment (if frequently changing):
swift
// ❌ Before: Environment
.environment(\.scrollOffset, scrollOffset)
// ✅ After: Direct parameter
ChildView(scrollOffset: scrollOffset)Fix B: Use @Observable model (if needed by many views):
swift
// 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
}Verification:
- Record new trace in Instruments
- Check Cause & Effect Graph — fewer views should update
- Performance should improve (smoother scrolling/animations)
修复A:从环境中移除(若值频繁变更):
swift
// ❌ 之前:使用环境
.environment(\.scrollOffset, scrollOffset)
// ✅ 之后:直接传递参数
ChildView(scrollOffset: scrollOffset)修复B:使用@Observable模型(若多个视图需要):
swift
// 不要在环境中存储原始类型:
@Observable class ScrollViewModel {
var offset: CGFloat = 0
}
// 视图依赖特定属性:
@Environment(ScrollViewModel.self) private var viewModel
var body: some View {
Text("\(viewModel.offset)") // 仅在offset变更时更新
}验证:
- 在Instruments中记录新的追踪数据
- 查看因果图 — 应更少视图更新
- 性能应提升(滚动/动画更流畅)
Pattern D5: Preview Diagnostics (Xcode 26)
模式D5:预览诊断(Xcode 26)
Time cost: 10 minutes
Symptom: Preview won't load or crashes with unclear error
When to use:
- Preview fails after basic fixes (swiftui-debugging skill)
- Error message unclear or generic
- Preview worked before, stopped suddenly
Investigation steps:
时间成本:10分钟
症状:预览无法加载或崩溃且错误信息不明确
适用场景:
- 基础修复后预览仍失败(swiftui-debugging技能)
- 错误信息不明确或通用
- 预览之前正常工作,突然停止
排查步骤:
1. Use Preview Diagnostics Button (2 min)
1. 使用预览诊断按钮(2分钟)
Location: Editor menu → Canvas → Diagnostics
What it shows:
- Detailed error messages
- Missing dependencies
- State initialization issues
- Preview-specific problems
位置:编辑器菜单 → 画布 → 诊断
显示内容:
- 详细错误信息
- 缺失的依赖项
- 状态初始化问题
- 预览特定问题
2. Check crash logs (3 min)
2. 检查崩溃日志(3分钟)
bash
undefinedbash
undefinedOpen crash logs directory
打开崩溃日志目录
open ~/Library/Logs/DiagnosticReports/
open ~/Library/Logs/DiagnosticReports/
Look for recent .crash files containing "Preview"
查找包含"Preview"的近期.crash文件
ls -lt ~/Library/Logs/DiagnosticReports/ | grep -i preview | head -5
**What to look for**:
- Fatal errors (array out of bounds, force unwrap nil)
- Missing module imports
- Framework initialization failuresls -lt ~/Library/Logs/DiagnosticReports/ | grep -i preview | head -5
**重点关注**:
- 致命错误(数组越界、强制解包nil)
- 缺失的模块导入
- 框架初始化失败3. Isolate the problem (5 min)
3. 隔离问题(5分钟)
Create minimal preview:
swift
// 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 crashCommon issues:
| Error | Cause | Fix |
|---|---|---|
| "Cannot find in scope" | Missing dependency | Add to preview (see example below) |
| "Fatal error: Unexpectedly found nil" | Optional unwrap failed | Provide non-nil value in preview |
| "No such module" | Import missing | Add import statement |
| Silent crash (no error) | State init with invalid value | Use safe defaults |
Fix patterns:
swift
// 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 indexVerification:
- Preview loads without errors
- Can interact with preview normally
- Changes reflect immediately
创建最小化预览:
swift
// 从空预览开始
#Preview {
Text("Test")
}
// 若此预览正常工作,逐步添加内容:
#Preview {
MyView() // 你的实际视图,但使用模拟数据
.environment(MockModel()) // 提供所有依赖项
}
// 找出导致崩溃的依赖项常见问题:
| 错误 | 原因 | 修复 |
|---|---|---|
| "Cannot find in scope" | 缺失依赖项 | 在预览中添加(见下方示例) |
| "Fatal error: Unexpectedly found nil" | 可选值强制解包失败 | 在预览中提供非nil值 |
| "No such module" | 缺失导入 | 添加导入语句 |
| 无声崩溃(无错误) | 状态初始化使用了无效值 | 使用安全默认值 |
修复模式:
swift
// 缺失@Environment
#Preview {
ContentView()
.environment(AppModel()) // 提供依赖项
}
// 缺失@EnvironmentObject(iOS 17之前)
#Preview {
ContentView()
.environmentObject(AppModel())
}
// 缺失ModelContainer(SwiftData)
#Preview {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try! ModelContainer(for: Item.self, configurations: config)
return ContentView()
.modelContainer(container)
}
// 状态使用无效默认值
@State var selectedIndex = 10 // ❌ 超出范围
let items = ["a", "b", "c"]
// 修复:安全默认值
@State var selectedIndex = 0 // ✅ 有效索引验证:
- 预览无错误加载
- 可正常与预览交互
- 变更会立即反映
Production Crisis Scenario
生产环境危机场景
The Situation
场景情况
Context:
- iOS 26 build shipped 2 days ago
- Users report "settings screen freezes when toggling features"
- 15% of users affected (reported via App Store reviews)
- VP asking for updates every 2 hours
- 8 hours until next deployment window closes
- Junior engineer suggests: "Let me try switching to @ObservedObject"
背景:
- iOS 26版本已于2天前发布
- 用户反馈“切换功能时设置界面冻结”
- 15%的用户受影响(通过App Store评论报告)
- VP每2小时询问一次进度
- 距离下一个部署窗口关闭还有8小时
- 初级工程师建议:“我试试换成@ObservedObject”
Red Flags — Resist These
危险信号 — 拒绝这些做法
If you hear ANY of these under deadline pressure, STOP and use diagnostic patterns:
❌ "Let me try different property wrappers and see what works"
- Random changes = guessing
- 80% chance of making it worse
❌ "It works on my device, must be iOS 26 bug"
- User reports are real
- 15% = systematic issue, not edge case
❌ "We can roll back if the fix doesn't work"
- App Store review takes 24 hours
- Rollback isn't instant
❌ "Add .id(UUID()) to force refresh"
- Destroys state preservation
- Hides root cause
❌ "Users will accept degraded performance for now"
- Once shipped, you're committed for 24 hours
- Bad reviews persist
若在截止日期压力下听到以下任何说法,立即停止并使用诊断模式:
❌ “我试试不同的属性包装器看看哪个有用”
- 随机变更=猜测
- 80%的概率会让问题更糟
❌ “在我的设备上正常工作,肯定是iOS 26的bug”
- 用户反馈是真实的
- 15%的受影响比例=系统性问题,而非边缘情况
❌ “如果修复无效我们可以回滚”
- App Store审核需要24小时
- 回滚并非即时操作
❌ “添加.id(UUID())强制刷新”
- 破坏状态保留
- 掩盖根本原因
❌ “用户现在会接受性能下降”
- 一旦发布,你将在24小时内无法更改
- 负面评论会持续存在
Mandatory Protocol (No Shortcuts)
强制流程(无捷径)
Total time budget: 90 minutes
总时间预算:90分钟
Phase 1: Reproduce (15 min)
阶段1:重现问题(15分钟)
bash
undefinedbash
undefined1. Get exact steps from user report
1. 从用户报告中获取确切步骤
2. Build Release mode
2. 构建Release版本
xcodebuild build -scheme YourApp -configuration Release
xcodebuild build -scheme YourApp -configuration Release
3. Test on device (not simulator)
3. 在设备上测试(而非模拟器)
4. Reproduce freeze 3+ times
4. 重现冻结问题3次以上
**If can't reproduce**: Ask for video recording or device logs from affected users
**若无法重现**:向受影响用户索要视频录制或设备日志Phase 2: Diagnose with Pattern D2 (30 min)
阶段2:使用模式D2诊断(30分钟)
bash
undefinedbash
undefinedLaunch Instruments with SwiftUI template
启动带有SwiftUI模板的Instruments
Command-I in Xcode
在Xcode中按Command-I
Record while reproducing freeze
重现冻结问题时录制追踪数据
Look for:
重点关注:
- Long View Body Updates (red bars)
- Long View Body Updates(红色条)
- Cause & Effect Graph showing update cascade
- 显示更新级联的因果图
**Find**:
- Which view is expensive?
- What data change triggered it?
- How many views updated?
**查找内容**:
- 哪个视图耗时过长?
- 触发更新的数据是什么?
- 有多少视图更新了?Phase 3: Apply Targeted Fix (20 min)
阶段3:应用针对性修复(20分钟)
Based on diagnostic findings:
If Long View Body Update:
swift
// Example finding: Formatter creation in body
// Fix: Move to cached formatterIf Cascade Update:
swift
// Example finding: All toggle views reading entire settings array
// Fix: Per-toggle view models with granular dependenciesIf Environment Issue:
swift
// Example finding: Environment value updating every frame
// Fix: Remove from environment, use direct parameter基于诊断结果:
若存在耗时视图体更新:
swift
// 示例发现:视图体中创建格式化器
// 修复:将格式化器移至缓存若存在级联更新:
swift
// 示例发现:所有开关视图都读取整个设置数组
// 修复:为每个开关使用粒度化视图模型若存在环境问题:
swift
// 示例发现:环境值每一帧都在更新
// 修复:从环境中移除,改为直接传递参数Phase 4: Verify (15 min)
阶段4:验证修复(15分钟)
bash
undefinedbash
undefinedRecord new Instruments trace
在Instruments中记录新的追踪数据
Compare before/after:
对比修复前后:
- Long updates eliminated?
- 耗时更新是否已消除?
- Update count reduced?
- 更新次数是否减少?
- Freeze gone?
- 冻结问题是否已解决?
Test on device 10+ times
在设备上测试10次以上
undefinedundefinedPhase 5: Deploy with Evidence (10 min)
阶段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."This shows:
- You diagnosed with evidence (not guessed)
- You understand the root cause
- You verified the fix
- You're shipping with confidence
发送给VP及团队的Slack消息:
"诊断完成:设置界面冻结是由ToggleRow视图体中的格式化器创建导致
(已通过SwiftUI Instrument的Long View Body Updates确认)。
每次点击开关都会为所有可见开关重新创建NumberFormatter + DateFormatter(每次点击创建20+个格式化器)。
修复方案:在SettingsViewModel中缓存格式化器,预格式化字符串。
验证结果:设置界面现在响应时间<16ms(之前为200ms+)。
正在部署2.1.1版本。接下来24小时会持续监控。"此消息表明:
- 你基于证据诊断(而非猜测)
- 你了解根本原因
- 你已验证修复有效
- 你对发布有信心
Time Cost Comparison
时间成本对比
Option A: Guess and Pray
选项A:猜测并祈祷
- Time to try random fixes: 30 min
- Time to deploy: 20 min
- Time to learn it failed: 24 hours (next App Store review)
- Total delay: 24+ hours
- User suffering: Continues through deployment window
- Risk: Made it worse, now TWO bugs
- 尝试随机修复的时间:30分钟
- 部署时间:20分钟
- 发现修复无效的时间:24小时(下一次App Store审核)
- 总延迟:24+小时
- 用户困扰:持续到下一个部署窗口
- 风险:问题恶化,现在存在两个bug
Option B: Diagnostic Protocol (This Skill)
选项B:诊断流程(此技能)
- Time to diagnose: 45 min
- Time to apply targeted fix: 20 min
- Time to verify: 15 min
- Time to deploy: 10 min
- Total time: 90 minutes
- User suffering: Stopped after 2 hours
- Confidence: High (evidence-based fix)
Savings: 22 hours + avoid making it worse
- 诊断时间:45分钟
- 应用针对性修复的时间:20分钟
- 验证时间:15分钟
- 部署时间:10分钟
- 总时间:90分钟
- 用户困扰:2小时后结束
- 信心:高(基于证据的修复)
节省:22小时 + 避免问题恶化
When Pressure is Legitimate
何时可以接受压力
Sometimes managers are right to push for speed. Accept the pressure IF:
✅ You've completed diagnostic protocol (90 minutes)
✅ You know exact view/operation causing issue
✅ You have targeted fix, not a guess
✅ You've verified in Instruments before shipping
✅ You're shipping WITH evidence, not hoping
Document your decision (same as above Slack template)
有时管理者催促加快速度是合理的。仅在满足以下所有条件时接受压力:
✅ 你已完成诊断流程(90分钟)
✅ 你明确知道导致问题的视图/操作
✅ 你有针对性修复方案,而非猜测
✅ 你已在Instruments中验证修复有效
✅ 你发布时附带证据,而非心存侥幸
记录你的决策(使用上述Slack模板格式)
Professional Script for Pushback
应对催促的专业话术
If pressured to skip diagnostics:
"I understand the urgency. Skipping diagnostics means 80% chance of shipping the wrong fix, committing us to 24 more hours of user suffering. The diagnostic protocol takes 90 minutes total and gives us evidence-based confidence. We'll have the fix deployed in under 2 hours, verified, with no risk of making it worse. The math says diagnostics is the fastest path to resolution."
若被要求跳过诊断:
"我理解紧迫性。跳过诊断意味着80%的概率发布错误修复,让用户再忍受24小时的困扰。诊断流程总共需要90分钟,能让我们获得基于证据的信心。我们将在2小时内部署经过验证的修复,不会有问题恶化的风险。从数据来看,诊断是最快的解决路径。"
Quick Reference Table
快速参考表
| Symptom | Likely Cause | First Check | Pattern | Fix Time |
|---|---|---|---|---|
| View doesn't update | Missing observer / Wrong state | Self._printChanges() | D1 | 10 min |
| View updates too often | Broad dependencies | Self._printChanges() → Instruments | D1 → D2 | 30 min |
| State resets | Identity change | .id() modifiers, conditionals | D3 | 15 min |
| Cascade updates | Environment issue | Environment modifiers | D4 | 20 min |
| Preview crashes | Missing deps / Bad init | Diagnostics button | D5 | 10 min |
| Intermittent issues | Identity or timing | Reproduce 30+ times | D3 | 30 min |
| Long updates (performance) | Expensive body operation | Instruments (SwiftUI + Time Profiler) | D2 | 30 min |
| 症状 | 可能原因 | 首次检查 | 模式 | 修复时间 |
|---|---|---|---|---|
| 视图不更新 | 缺失观察者 / 错误的状态 | Self._printChanges() | D1 | 10分钟 |
| 视图更新过于频繁 | 宽泛依赖 | Self._printChanges() → Instruments | D1 → D2 | 30分钟 |
| 状态重置 | 标识变更 | .id()修饰符、条件判断 | D3 | 15分钟 |
| 级联更新 | 环境问题 | 环境修饰符 | D4 | 20分钟 |
| 预览崩溃 | 缺失依赖 / 错误初始化 | 诊断按钮 | D5 | 10分钟 |
| 间歇性问题 | 标识或时序 | 重现30次以上 | D3 | 30分钟 |
| 耗时更新(性能) | 视图体操作昂贵 | Instruments(SwiftUI + Time Profiler) | D2 | 30分钟 |
Decision Framework
决策框架
Before shipping ANY fix:
| Question | Answer Yes? | Action |
|---|---|---|
| Have you used Self._printChanges()? | No | STOP - Pattern D1 (5 min) |
| Have you run SwiftUI Instrument? | No | STOP - Pattern D2 (25 min) |
| Can you explain in one sentence what caused the issue? | No | STOP - you're guessing |
| Have you verified the fix in Instruments? | No | STOP - test before shipping |
| Did you check for simpler explanations? | No | STOP - review diagnostic patterns |
Answer YES to all five → Ship with confidence
发布任何修复前:
| 问题 | 答案是? | 操作 |
|---|---|---|
| 你是否使用了Self._printChanges()? | 否 | 停止 - 使用模式D1(5分钟) |
| 你是否运行了SwiftUI Instrument? | 否 | 停止 - 使用模式D2(25分钟) |
| 你能否用一句话解释问题原因? | 否 | 停止 - 你在猜测 |
| 你是否已在Instruments中验证修复? | 否 | 停止 - 发布前测试 |
| 你是否检查了更简单的解释? | 否 | 停止 - 回顾诊断模式 |
全部回答是 → 充满信心地发布
Common Mistakes
常见误区
Mistake 1: "I added @Observable and it fixed it"
误区1:“我添加了@Observable就修复了问题”
Why it's wrong: You don't know WHY it fixed it
- Might work now, break later
- Might have hidden another bug
Right approach:
- Use Pattern D1 (Self._printChanges()) to see BEFORE state
- Apply @Observable
- Use Pattern D1 again to see AFTER state
- Understand exactly what changed
错误原因:你不知道为什么修复有效
- 现在可能正常工作,但以后可能崩溃
- 可能掩盖了另一个bug
正确做法:
- 使用模式D1(Self._printChanges())查看添加前的状态
- 应用@Observable
- 再次使用模式D1查看添加后的状态
- 明确了解发生了什么变化
Mistake 2: "Instruments is too slow for quick fixes"
误区2:“Instruments对于快速修复来说太慢了”
Why it's wrong: Guessing is slower when you're wrong
- 25 min diagnostic = certain fix
- 5 min guess × 3 failed attempts = 15 min + still broken
Right approach:
- Always profile for production issues
- Use Self._printChanges() for simple cases
错误原因:猜测错误时会更慢
- 25分钟的诊断=确定的修复
- 5分钟的猜测×3次失败=15分钟+问题仍未解决
正确做法:
- 生产环境问题始终要进行性能分析
- 简单场景使用Self._printChanges()
Mistake 3: "The fix works, I don't need to verify"
误区3:“修复有效,我不需要验证”
Why it's wrong: Manual testing ≠ verification
- Might work for your specific test
- Might fail for edge cases
- Might have introduced performance regression
Right approach:
- Always verify in Instruments after fix
- Compare before/after traces
- Test edge cases (empty data, large data, etc.)
错误原因:手动测试≠验证
- 可能仅在你的特定测试场景下有效
- 可能在边缘场景下失败
- 可能引入性能回归
正确做法:
- 修复后始终在Instruments中验证
- 对比修复前后的追踪数据
- 测试边缘场景(空数据、大数据等)
Quick Command Reference
快速命令参考
Instruments Commands
Instruments命令
bash
undefinedbash
undefinedLaunch Instruments with SwiftUI template
启动带有SwiftUI模板的Instruments
1. In Xcode: Command-I
1. 在Xcode中:Command-I
2. Or from command line:
2. 或从命令行启动:
open -a Instruments
open -a Instruments
Build in Release mode (required for accurate profiling)
以Release模式构建(性能分析必需)
xcodebuild build -scheme YourScheme -configuration Release
xcodebuild build -scheme YourScheme -configuration Release
Clean derived data if needed
必要时清理派生数据
rm -rf ~/Library/Developer/Xcode/DerivedData
undefinedrm -rf ~/Library/Developer/Xcode/DerivedData
undefinedSelf._printChanges() Debug Pattern
Self._printChanges() 调试模式
swift
// Add temporarily to view body
var body: some View {
let _ = Self._printChanges() // Shows update reason
// Your view code
}Remember: Remove before committing!
swift
// 临时添加到视图体
var body: some View {
let _ = Self._printChanges() // 显示更新原因
// 你的视图代码
}注意:提交前务必移除!
Preview Diagnostics
预览诊断
bash
undefinedbash
undefinedCheck preview crash logs
检查预览崩溃日志
open ~/Library/Logs/DiagnosticReports/
open ~/Library/Logs/DiagnosticReports/
Filter for recent preview crashes
筛选近期预览崩溃日志
ls -lt ~/Library/Logs/DiagnosticReports/ | grep -i preview | head -5
ls -lt ~/Library/Logs/DiagnosticReports/ | grep -i preview | head -5
Xcode menu path:
Xcode菜单路径:
Editor → Canvas → Diagnostics
Editor → Canvas → Diagnostics
undefinedundefinedEnvironment Search
环境搜索
bash
undefinedbash
undefinedFind environment modifiers
查找环境修饰符
grep -r ".environment(" --include="*.swift" .
grep -r ".environment(" --include="*.swift" .
Find environment object usage
查找环境对象使用
grep -r "@Environment" --include="*.swift" .
grep -r "@Environment" --include="*.swift" .
Find view identity modifiers
查找视图标识修饰符
grep -r ".id(" --include="*.swift" .
undefinedgrep -r ".id(" --include="*.swift" .
undefinedInstruments Navigation
Instruments导航
In Instruments (after recording):
- Select SwiftUI track
- Expand to see:
- Update Groups lane
- Long View Body Updates lane
- Long Representable Updates lane
- Click Long View Body Updates summary
- Right-click update → "Set Inspection Range and Zoom"
- Switch to Time Profiler track
- Find your view in call stack (Command-F)
Cause & Effect Graph:
- Expand hierarchy in detail pane
- Hover over view name → Click arrow
- Choose "Show Cause & Effect Graph"
- Click nodes to see:
- State change node → Backtrace
- View body node → Dependencies
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"
- 点击节点查看:
- 状态变更节点 → 回溯信息
- 视图体节点 → 依赖项
Resources
参考资源
WWDC: 2025-306, 2023-10160, 2023-10149, 2021-10022
Docs: /xcode/understanding-hitches-in-your-app, /xcode/analyzing-hangs-in-your-app, /swiftui/managing-model-data-in-your-app
Skills: axiom-swiftui-debugging, axiom-swiftui-performance, axiom-swiftui-layout, axiom-xcode-debugging
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