关于 swiftui-debugging
面向 SwiftUI 开发者的调试技能,通过性能分析、视图身份检查和轻量埋点,将性能问题转化为有证据支撑的诊断与针对性修复。
主要功能
- 01追踪视图 body 重新求值
- 02诊断视图身份变化
- 03检查列表与网格加载
- 04使用 Instruments 和日志
- 05实施针对性性能修复
适用场景
- 01排查意外的 body 更新
- 02解决状态重置与重复出现
- 03定位滚动或初始加载缓慢
- 04将耗时操作移出 body
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mengto/ skills --skill swiftui-debugging安装后在 Claude Code 中输入/swiftui-debugging确认已加载。
详细介绍
SwiftUI 调试
使用此技能将 SwiftUI 性能问题转化为简短的证据分析流程:找出发生了什么变化,确认 SwiftUI 是在重新创建视图还是仅重新求值,然后实施范围最小的修复。
工作流
-
重现或定位问题。
- 编辑前,先搜索提到的视图、模型、列表/网格或修饰符链。
- 只有在无法从代码库推断出目标视图或交互时,才请求最小复现示例。
- 相较于猜测,优先查看性能分析跟踪、
_printChanges()输出、Console 日志或明确的交互路径。
-
选择诊断路径。
- 意外的 body 调用或嘈杂的
_printChanges()输出:阅读references/body-reevaluation.md。 - 状态重置、
onAppear重复触发、动画异常、焦点丢失或滚动位置重置:阅读references/view-identity.md。 - 列表、网格、滚动视图缓慢,或包含大量条目时初始加载缓慢:阅读
references/lazy-loading.md。 AnyView、在body中创建对象、开销较大的排序/筛选、过大的图像或重复写入状态:阅读references/common-pitfalls.md。
- 意外的 body 调用或嘈杂的
-
在进行大范围重写之前,先进行轻量检测。
- 在最可疑的视图中使用
let _ = Self._printChanges()。 - 如果代码检查无法解释开销,请使用 Instruments 的 SwiftUI 模板。
- 使用
os.Logger或os_signpost为开销较大的执行路径添加自定义计数。
- 在最可疑的视图中使用
-
通过减少失效、保留标识或转移工作来修复问题。
- 将状态下移到实际使用它的视图中。
- 拆分大型视图,避免不相关的状态变化导致开销较大的子树重新求值。
- 为
ForEach和.id()使用稳定标识符。 - 对于大型集合,将急切加载容器替换为
List或惰性堆栈/网格。 - 将繁重的格式化、解码、排序、筛选和图像准备工作移出
body。
-
验证问题是否解决,然后清理临时检测代码,除非用户希望保留。
快速分诊
| 症状 | 首要怀疑对象 | 参考资料 |
|---|---|---|
无关值变化时 body 也会运行 | 过度观察、父视图重新创建、状态放置过高 | body-reevaluation.md |
_printChanges() 显示 @identity changed | 不稳定的 .id()、条件结构变化 | view-identity.md |
| 滚动视图一次性创建数百行 | ScrollView 中使用 VStack/HStack、未使用惰性容器 | lazy-loading.md |
相同内容反复触发 onAppear | 标识 churn 或惰性单元格回收 | view-identity.md、lazy-loading.md |
| 在字段中输入时整个屏幕都会更新 | 范围过大的 ObservableObject、状态放置过高 | body-reevaluation.md |
| 初始加载或动画卡顿 | body 中存在开销较大的工作、图像解码、急切布局 | common-pitfalls.md |
| 动态视图类型的差异比较效果似乎不佳 | AnyView 或类型擦除的分支 | common-pitfalls.md |
实用工具
Self._printChanges()
var body: some View {
let _ = Self._printChanges()
// view content
}
常见输出的含义如下:
| 输出 | 含义 |
|---|---|
@self changed | 父视图重新创建了此视图值。检查父视图失效情况和输入是否相等。 |
@identity changed | SwiftUI 销毁并重新创建了此视图。检查 .id()、ForEach 的 id 和分支结构。 |
_property changed | 存储属性、状态、绑定或环境值发生了变化。 |
Instruments
使用 Xcode 的 SwiftUI 模板检查 View Body 计数、View Properties 变化和 Core Animation 提交次数。按计数或持续时间排序,重现缓慢的交互,并将修改重点放在最耗时的视图类型上。
自定义日志
使用 os.Logger 快速统计本地次数;如果需要与 Instruments 中的时间线关联,则使用 os_signpost。
审查清单
- 不使用不稳定的
.id()值,例如UUID()、Date()、随机值或可变数组索引。 ForEach使用稳定且唯一的模型标识符。- 不要仅为了切换同一视图上的修饰符而使用条件分支。
- 对于大型集合,使用
List、LazyVStack、LazyHStack、LazyVGrid或LazyHGrid。 - 视图只观察其实际显示或修改的模型属性。
- 在部署目标允许时,优先使用
@Observable,而非宽泛使用ObservableObject。 - 不要在
body中创建格式化器、解码器、谓词、正则表达式、视图模型或解码图像。 - 适时在发生变化时执行开销较大的筛选、排序和布局计算,将其结果缓存为状态,或在主执行器之外执行。
- 最终交付前,移除临时的
_printChanges()、打印日志和标记点,或明确标注其用途。
API 可用性
| API / 技术 | 最低平台版本 |
|---|---|
Self._printChanges() | iOS 15 / macOS 12 时代的 SwiftUI 诊断功能 |
ObservableObject | iOS 13 / macOS 10.15 |
@Observable | iOS 17 / macOS 14 |
LazyVStack、LazyHStack、LazyVGrid、LazyHGrid | iOS 14 / macOS 11 |
.id() | iOS 13 / macOS 10.15 |
TimelineView | iOS 15 / macOS 12 |
| Instruments SwiftUI 模板 | Xcode 14+ |
参考资料
references/body-reevaluation.md:依赖失效、_printChanges()、观察范围和状态放置。references/view-identity.md:结构身份、.id()、ForEach、分支身份和状态丢失。references/lazy-loading.md:急切与延迟容器、列表/网格选择、滚动性能和延迟加载的陷阱。references/common-pitfalls.md:AnyView、对象分配、开销较大的body操作、图像解码、冗余状态和隐藏视图。references/upstream-license.md:源材料的上游署名和 MIT 许可证声明。
评论
支持 Markdown