axiom-swiftui-debugging-diag by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-debugging-diag在以下情况时使用此技能:
axiom-swiftui-debugging 技能模式但问题仍然存在Self._printChanges() 显示意外模式 — 视图在不该更新时更新,或在该更新时不更新在压力下,你可能会尝试走捷径来掩盖问题而不是诊断问题。切勿执行以下操作:
❌ 随意更改 @State/@Observable 进行猜测
❌ 添加 .id(UUID()) 来强制更新
❌ (iOS 17+)
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
❌ 忽略间歇性问题 ("有时工作")
❌ 在不理解的情况下发布
在深入诊断模式之前,建立基线环境:
# 1. 验证 Instruments 设置
xcodebuild -version # 必须是 Xcode 26+ 以使用 SwiftUI Instrument
# 2. 为性能分析构建 Release 模式
xcodebuild build -scheme YourScheme -configuration Release
# 3. 如果调查预览问题,清除派生数据
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)
时间成本:5 分钟
症状:需要确切了解视图 body 运行的原因
何时使用:
技术:
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)")
}
}
}
输出解释:
# 场景 1: 视图参数改变
MyView: @self changed
→ 父视图传递了新的 MyView 实例
→ 检查父视图代码 - 是什么触发了重新创建?
# 场景 2: 状态属性改变
MyView: count changed
→ 本地 @State 触发了更新
→ 如果你修改了 count,这是预期的
# 场景 3: 环境属性改变
MyView: @self changed # 环境是 @self 的一部分
→ 环境值改变(配色方案、区域设置、自定义值)
→ 模式 D4: 检查环境依赖
# 场景 4: 无日志输出
→ Body 未被调用
→ 模式 D3: 视图标识调查
常见发现:
验证:
Self._printChanges() 调用交叉参考:对于复杂情况,使用模式 D2 (SwiftUI Instrument)
时间成本:25 分钟
症状:Self._printChanges() 无法完全解释的复杂更新模式
何时使用:
先决条件:
步骤:
# 构建 Release 版本
xcodebuild build -scheme YourScheme -configuration Release
# 启动 Instruments
# 在 Xcode 中按 Command-I
# 选择 "SwiftUI" 模板
修复:将开销大的操作移到模型层,缓存结果
图节点:
[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
常见模式:
# 模式 A: 单个视图更新 (良好)
[Gesture] → [State Change in ViewModelA] → [ViewA body]
# 模式 B: 所有视图更新 (糟糕 - 广泛的依赖)
[Gesture] → [Array change] → [All list item views update]
└─ Fix: Use granular view models, one per item
# 模式 C: 通过环境级联 (检查)
[State Change] → [Environment write] → [Many view bodies check]
└─ If environment value changes frequently → Pattern D4 fix
点击节点:
验证:
交叉参考:axiom-swiftui-performance 技能,了解详细的 Instruments 工作流程
时间成本:15 分钟
症状:@State 值意外重置,或视图不执行动画
何时使用:
根本原因:视图标识意外更改
调查步骤:
// ❌ 问题: 标识随条件改变
if showDetails {
CounterView() // 每次 showDetails 切换时获得新标识
}
// ✅ 修复: 使用 .opacity()
CounterView()
.opacity(showDetails ? 1 : 0) // 始终是相同的标识
查找:在代码库中搜索包含状态的 if/else 内的视图
// ❌ 问题: .id() 随数据更改而改变
DetailView()
.id(item.id + "-\(isEditing)") // ID 随 isEditing 改变
// ✅ 修复: 稳定的 ID
DetailView()
.id(item.id) // 稳定的 ID
查找:在代码库中搜索 .id( — 检查 ID 值是否改变
// ❌ 错误: 基于索引的 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)
}
查找:搜索 ForEach — 验证唯一、稳定的 ID
修复模式:
| 问题 | 修复 |
|---|---|
| 条件中的视图 | 改用 .opacity() |
| .id() 更改过于频繁 | 使用稳定的标识符 |
| ForEach 跳动 | 使用唯一、稳定的 ID(UUID 或服务器 ID) |
| 导航时状态重置 | 检查 NavigationStack 路径管理 |
验证:
时间成本:10 分钟
症状:不相关数据更改时,许多视图更新
何时使用:
根本原因:环境中的值频繁更改,或者太多视图读取环境
调查步骤:
# 在当前项目中搜索环境修饰符
grep -r "\.environment(" --include="*.swift" .
寻找:
// ❌ 糟糕: 频繁更改的值
.environment(\.scrollOffset, scrollOffset) // 每秒更新 60+ 次
.environment(model) // 如果 model 频繁更新
// ✅ 良好: 稳定的值
.environment(\.colorScheme, .dark)
.environment(appModel) // 如果 appModel 很少更改
使用模式 D2 (Instruments),检查因果图:
问题:
修复 A: 从环境中移除(如果频繁更改):
// ❌ 之前: 环境
.environment(\.scrollOffset, scrollOffset)
// ✅ 之后: 直接参数
ChildView(scrollOffset: scrollOffset)
修复 B: 使用 @Observable 模型(如果需要被许多视图使用):
// 而不是在环境中存储原始值:
@Observable class ScrollViewModel {
var offset: CGFloat = 0
}
// 视图依赖于特定属性:
@Environment(ScrollViewModel.self) private var viewModel
var body: some View {
Text("\(viewModel.offset)") // 仅在 offset 更改时更新
}
验证:
时间成本:10 分钟
症状:预览无法加载或崩溃且错误信息不明确
何时使用:
调查步骤:
位置:编辑器菜单 → Canvas → Diagnostics
显示内容:
# 打开崩溃日志目录
open ~/Library/Logs/DiagnosticReports/
# 查找包含 "Preview" 的最近 .crash 文件
ls -lt ~/Library/Logs/DiagnosticReports/ | grep -i preview | head -5
查找内容:
创建最小预览:
// 从空预览开始
#Preview {
Text("Test")
}
// 如果这个有效,逐步添加:
#Preview {
MyView() // 你的实际视图,但使用模拟数据
.environment(MockModel()) // 提供所有依赖项
}
// 找出导致崩溃的依赖项
常见问题:
| 错误 | 原因 | 修复 |
|---|---|---|
| "Cannot find in scope" | 缺少依赖项 | 添加到预览中(见下方示例) |
| "Fatal error: Unexpectedly found nil" | 可选值解包失败 | 在预览中提供非 nil 值 |
| "No such module" | 缺少导入语句 | 添加 import 语句 |
| 静默崩溃(无错误) | 使用无效值初始化状态 | 使用安全的默认值 |
修复模式:
// 缺少 @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 // ✅ 有效索引
验证:
背景:
如果你在截止日期压力下听到任何这些说法,停止并使用诊断模式:
❌ "让我试试不同的属性包装器,看看哪个有效"
❌ "在我的设备上有效,肯定是 iOS 26 的 bug"
❌ "如果修复无效,我们可以回滚"
❌ "添加 .id(UUID()) 来强制刷新"
❌ "用户现在会接受性能下降"
总时间预算:90 分钟
# 1. 从用户报告中获取确切步骤
# 2. 构建 Release 模式
xcodebuild build -scheme YourApp -configuration Release
# 3. 在设备上测试(非模拟器)
# 4. 复现冻结 3+ 次
如果无法复现:向受影响的用户索要视频录制或设备日志
# 使用 SwiftUI 模板启动 Instruments
# 在 Xcode 中按 Command-I
# 在复现冻结时记录
# 查找:
# - Long View Body Updates (红色条)
# - Cause & Effect Graph 显示更新级联
查找:
基于诊断发现:
如果是长视图 Body 更新:
// 示例发现: 在 body 中创建格式化程序
// 修复: 移动到缓存的格式化程序
如果是级联更新:
// 示例发现: 所有切换视图读取整个设置数组
// 修复: 每个切换视图使用具有细粒度依赖关系的视图模型
如果是环境问题:
// 示例发现: 环境值每帧更新
// 修复: 从环境中移除,使用直接参数
# 记录新的 Instruments 追踪
# 比较修复前后:
# - 长更新消除了吗?
# - 更新次数减少了吗?
# - 冻结消失了吗?
# 在设备上测试 10+ 次
Slack 发送给副总裁和团队:
"诊断完成:设置屏幕冻结由 ToggleRow body 中的格式化程序创建引起
(通过 SwiftUI Instrument 的 Long View Body Updates 确认)。
每次切换点击都会为所有可见切换重新创建 NumberFormatter + DateFormatter
(每次点击创建 20+ 个格式化程序)。
修复:在 SettingsViewModel 中缓存格式化程序,预格式化字符串。
验证:设置屏幕现在响应时间 <16ms(之前是 200ms+)。
现在部署构建 2.1.1。将在接下来的 24 小时内监控。"
这表明:
节省:22 小时 + 避免让情况更糟
有时管理者推动速度是正确的。接受压力,如果:
✅ 你已完成诊断协议(90 分钟) ✅ 你知道导致问题的确切视图/操作 ✅ 你有针对性修复,而非猜测 ✅ 你在发布前已在 Instruments 中验证 ✅ 你带着证据发布,而非希望
记录你的决策(与上面的 Slack 模板相同)
如果被要求跳过诊断:
"我理解紧迫性。跳过诊断意味着有 80% 的几率发布错误的修复,让我们再承受 24 小时用户受影响。诊断协议总共需要 90 分钟,并给予我们基于证据的信心。我们将在 2 小时内完成修复、验证并部署,且没有让情况更糟的风险。从数学上讲,诊断是解决问题的最快路径。"
| 症状 | 可能原因 | 首选检查 | 模式 | 修复时间 |
|---|---|---|---|---|
| 视图不更新 | 缺少观察者 / 错误的状态 | Self._printChanges() | D1 | 10 分钟 |
| 视图更新过于频繁 | 广泛的依赖关系 | Self._printChanges() → Instruments | D1 → D2 | 30 分钟 |
| 状态重置 | 标识更改 | .id() 修饰符、条件语句 | D3 | 15 分钟 |
| 级联更新 | 环境问题 | 环境修饰符 | D4 | 20 分钟 |
| 预览崩溃 | 缺少依赖项 / 错误的初始化 | 诊断按钮 | D5 | 10 分钟 |
| 间歇性问题 | 标识或时序问题 | 复现 30+ 次 | D3 | 30 分钟 |
| 长更新(性能) | 开销大的 body 操作 | Instruments (SwiftUI + Time Profiler) | D2 | 30 分钟 |
在发布任何修复之前:
| 问题 | 答案是? | 操作 |
|---|---|---|
| 你使用过 Self._printChanges() 吗? | 否 | 停止 - 模式 D1 (5 分钟) |
| 你运行过 SwiftUI Instrument 吗? | 否 | 停止 - 模式 D2 (25 分钟) |
| 你能用一句话解释是什么导致了问题吗? | 否 | 停止 - 你在猜测 |
| 你已经在 Instruments 中验证了修复吗? | 否 | 停止 - 发布前测试 |
| 你检查过更简单的解释吗? | 否 | 停止 - 回顾诊断模式 |
对所有五个问题都回答是 → 有信心地发布
为什么是错的:你不知道为什么它修复了
正确方法:
为什么是错的:猜错时更慢
正确方法:
为什么是错的:手动测试 ≠ 验证
正确方法:
# 使用 SwiftUI 模板启动 Instruments
# 1. 在 Xcode 中: Command-I
# 2. 或从命令行:
open -a Instruments
# 在 Release 模式下构建(准确性能分析所需)
xcodebuild build -scheme YourScheme -configuration Release
# 如果需要,清理派生数据
rm -rf ~/Library/Developer/Xcode/DerivedData
// 临时添加到视图 body
var body: some View {
let _ = Self._printChanges() // 显示更新原因
// 你的视图代码
}
记住:提交前移除!
# 检查预览崩溃日志
open ~/Library/Logs/DiagnosticReports/
# 过滤最近的预览崩溃
ls -lt ~/Library/Logs/DiagnosticReports/ | grep -i preview | head -5
# Xcode 菜单路径:
# Editor → Canvas → Diagnostics
# 查找环境修饰符
grep -r "\.environment(" --include="*.swift" .
# 查找环境对象使用情况
grep -r "@Environment" --include="*.swift" .
# 查找视图标识修饰符
grep -r "\.id(" --include="*.swift" .
在 Instruments 中(记录后):
因果图:
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
每周安装次数
104
仓库
GitHub 星标数
606
首次出现
2026年1月21日
安全审计
安装于
opencode89
codex84
claude-code83
gemini-cli81
cursor80
github-copilot77
Use this skill when:
axiom-swiftui-debugging skill patterns but issue persistsUnder pressure, you'll be tempted to shortcuts that hide problems instead of diagnosing them. NEVER do these :
❌ Guessing with random @State/@Observable changes
❌ Adding .id(UUID()) to force updates
❌ Using ObservableObject when @Observable would work (iOS 17+)
❌ Ignoring intermittent issues ("works sometimes")
❌ Shipping without understanding
Before diving into diagnostic patterns, establish baseline environment:
# 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
Time cost : 5 minutes Why : Wrong Xcode version or Debug mode produces misleading profiling data
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)
Time cost : 5 minutes
Symptom : Need to understand exactly why view body runs
When to use :
Technique :
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 :
# 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
Common discoveries :
"@self changed" when you don't expect
Property shows changed but you didn't change it
Multiple properties changing together
Verification :
Self._printChanges() call before committingCross-reference : For complex cases, use Pattern D2 (SwiftUI Instrument)
Time cost : 25 minutes
Symptom : Complex update patterns that Self._printChanges() can't fully explain
When to use :
Prerequisites :
Steps :
# Build Release
xcodebuild build -scheme YourScheme -configuration Release
# Launch Instruments
# Press Command-I in Xcode
# Choose "SwiftUI" template
Fix : Move expensive operation to model layer, cache result
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 appear
Common patterns :
# 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
Click on nodes :
Verification :
Cross-reference : axiom-swiftui-performance skill for detailed Instruments workflows
Time cost : 15 minutes
Symptom : @State values reset unexpectedly, or views don't animate
When to use :
Root cause : View identity changed unexpectedly
Investigation steps :
// ❌ 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
Find : Search codebase for views inside if/else that hold state
// ❌ PROBLEM: .id() changes when data changes
DetailView()
.id(item.id + "-\(isEditing)") // ID changes with isEditing
// ✅ FIX: Stable ID
DetailView()
.id(item.id) // Stable ID
Find : Search codebase for .id( — check if ID values change
// ❌ 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 ForEach — verify unique, stable IDs
Fix patterns :
| Issue | Fix |
|---|---|
| View in conditional | Use .opacity() instead |
| .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 :
Time cost : 10 minutes
Symptom : Many views updating when unrelated data changes
When to use :
Root cause : Frequently-changing value in environment OR too many views reading environment
Investigation steps :
# Search for environment modifiers in current project
grep -r "\.environment(" --include="*.swift" .
Look for :
// ❌ 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
Using Pattern D2 (Instruments), check Cause & Effect Graph:
Questions :
Fix A: Remove from environment (if frequently changing):
// ❌ Before: Environment
.environment(\.scrollOffset, scrollOffset)
// ✅ After: Direct parameter
ChildView(scrollOffset: scrollOffset)
Fix B: Use @Observable model (if needed by many views):
// 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 :
Time cost : 10 minutes
Symptom : Preview won't load or crashes with unclear error
When to use :
Investigation steps :
Location : Editor menu → Canvas → Diagnostics
What it shows :
# 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
What to look for :
Create minimal preview :
// 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
Common 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 :
// 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
Verification :
Context :
If you hear ANY of these under deadline pressure, STOP and use diagnostic patterns :
❌ "Let me try different property wrappers and see what works"
❌ "It works on my device, must be iOS 26 bug"
❌ "We can roll back if the fix doesn't work"
❌ "Add .id(UUID()) to force refresh"
❌ "Users will accept degraded performance for now"
Total time budget : 90 minutes
# 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
If can't reproduce : Ask for video recording or device logs from affected users
# 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
Find :
Based on diagnostic findings:
If Long View Body Update :
// Example finding: Formatter creation in body
// Fix: Move to cached formatter
If Cascade Update :
// Example finding: All toggle views reading entire settings array
// Fix: Per-toggle view models with granular dependencies
If Environment Issue :
// Example finding: Environment value updating every frame
// Fix: Remove from environment, use direct parameter
# Record new Instruments trace
# Compare before/after:
# - Long updates eliminated?
# - Update count reduced?
# - Freeze gone?
# Test on device 10+ times
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 :
Savings : 22 hours + avoid making it worse
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)
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."
| 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 |
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
Why it's wrong : You don't know WHY it fixed it
Right approach :
Why it's wrong : Guessing is slower when you're wrong
Right approach :
Why it's wrong : Manual testing ≠ verification
Right approach :
# 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
// Add temporarily to view body
var body: some View {
let _ = Self._printChanges() // Shows update reason
// Your view code
}
Remember : Remove before committing!
# 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" .
In Instruments (after recording) :
Cause & Effect Graph:
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
Weekly Installs
104
Repository
GitHub Stars
606
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode89
codex84
claude-code83
gemini-cli81
cursor80
github-copilot77
AI PR 分类工具 - 自动化代码审查分析与成本优化 | pr-triage
760 周安装
50+ AI智能体专家集合 | 适用于Claude Code、Cursor、Copilot等开发工具的专业人格
773 周安装
GitHub Copilot for Azure 集成测试失败自动创建 Issue 工具 | 测试自动化
822 周安装
CreateOS云部署平台:一键部署AI智能体、API、Docker镜像到生产环境
811 周安装
技术文档编写指南:为不同受众撰写清晰可维护的API、README、架构文档
936 周安装
Railway项目管理技能:列出、切换、重命名、配置项目和工作区
851 周安装
| Identity or timing |
| Reproduce 30+ times |
| D3 |
| 30 min |
| Long updates (performance) | Expensive body operation | Instruments (SwiftUI + Time Profiler) | D2 | 30 min |