关于 swiftui-performance
帮助 SwiftUI 开发者从可复现的问题入手审查性能,并依据代码或 Instruments 跟踪证据诊断和验证改进。强调针对性修复,而非未经测量的大范围重构。
主要功能
- 01代码审查与性能问题分诊
- 02Instruments 跟踪采集和分析
- 03状态依赖与视图身份优化
- 04延迟加载和观察范围调整
- 05基线对比与修复验证
适用场景
- 01调查滚动列表更新或卡顿
- 02定位视图更新频繁的原因
- 03分析主线程上的耗时工作
- 04对比优化前后的性能指标
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill swiftui-performance安装后在 Claude Code 中输入/swiftui-performance确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/demystify-swiftui-performance-wwdc23.mdoptimizing-swiftui-performance-instruments.mdunderstanding-hangs-in-your-app.mdunderstanding-improving-swiftui-performance.mdwwdc-session-sources.md详细介绍
SwiftUI 性能
从可复现的问题出发,通过测量来审查和改进 SwiftUI 视图性能。动画设计请转至 swiftui-animation,生产遥测请转至 metrickit,所有权/泄漏分析请转至 ios-memgraph-analysis,导航行为请转至 swiftui-navigation,状态架构请转至 swiftui-patterns,布局构建请转至 swiftui-layout-components。
目录
- 工作流决策树
- 1. 代码优先审查
- 2. 指导用户进行性能分析
- 3. 分析与诊断
- 4. 修复
- 常见代码异味(及修复方法)
- 5. 验证
- 输出
- Instruments 性能分析
- 标识与生命周期
- 延迟加载模式
- 状态与 Observation 优化
- 常见错误
- 审查清单
- 参考资料
工作流决策树
- 提供了代码:先审查代码,并将发现标注为假设。
- 只有问题症状:收集相关的最小视图、数据流、复现步骤、设备、OS 和构建配置。
- 审查无法得出结论:在提出大范围重构建议前,先收集跟踪记录或轨道截图。
对代码和跟踪记录分析,都使用以下分诊清单:
- 状态依赖范围过广或失效风暴
- 列表标识不稳定或根视图条件切换
- 在
body中进行格式化、排序、解码或同步 I/O - 布局/几何反馈循环及图像尺寸过大
- 对大型视图层级应用隐式动画
1. 代码优先审查
将分诊清单中的每个可疑项对应到具体代码。通过代码引用说明可能原因,但在跟踪记录证实开销之前,将这些发现标注为有代码依据的假设。证据不足时,建议创建最小复现或进行测量。
2. 指导用户进行性能分析
尽可能在真实设备上使用 SwiftUI Instruments 模板和 Release 构建。复现确切的交互,并根据需要捕获 SwiftUI 轨道、Time Profiler 和 Hangs/Hitches。请用户提供跟踪记录,或提供轨道和调用树的截图。
3. 分析与诊断
根据跟踪记录证据应用相同的分诊清单。将耗时较长或频繁发生的 SwiftUI 更新与 Time Profiler 调用树及复现的交互关联起来。区分有跟踪记录依据的发现与有代码依据的假设,并指出可消除剩余不确定性的下一项测量。
4. 修复
进行针对性修复:
- 缩小状态作用范围(将
@State/@Observable移近叶视图)。 - 为
ForEach和列表稳定标识。 - 将繁重工作移出
body,放入模型层预计算、输入变化时更新的显式派生值、记忆化辅助方法或后台处理。仅当视图同时拥有该值及其更新生命周期时才使用@State;它不是任意计算的通用缓存。 - 仅当相等性比较比重新计算子树更省开销,且被比较的输入具有稳定的值语义时,才使用
equatable()。 - 在渲染前对图像降采样。
- 简化布局,或尽可能使用固定尺寸。
常见代码异味(及修复方法)
| 异味 | 应查找的证据 | 针对性修复 |
|---|---|---|
在 body 中格式化、排序、筛选或解码 | body 更新频繁或耗时较长,且调用树成本相应增加 | 在输入变化时重新计算;在主 actor 外进行降采样或解码 |
UUID() 或不稳定的 id: \.self | 行被重新创建、状态丢失、更新过多 | 使用稳定的模型标识 |
根视图的 if/else 切换 | 切换时状态重置或更新激增 | 在语义允许时,将条件内容或修饰符局部化 |
| 广泛读取模型 | 许多无关视图同时更新 | 传递范围更窄的值,或将读取移入职责明确的子视图 |
| 布局期间写入几何信息 | 布局/更新循环反复发生 | 为变化设置阈值,或用稳定布局替换反馈路径 |
5. 验证
请用户重新运行相同的采集,并与基线指标进行比较。 如果用户提供了数据,请总结差异(CPU、帧丢失、内存峰值)。
输出
提供:
- 简短的指标表(如有数据,列出优化前后)。
- 主要问题(按影响排序)。
- 建议的修复及预估工作量。
Instruments 性能分析
在 Instruments 中使用 SwiftUI 模板(按 Cmd+I 进行性能分析)。当前 SwiftUI 轨道包括 Update Groups、Long View Body Updates、Long Representable Updates / Representable Updates、Other Long Updates / Other Updates,以及 Cause & Effect Graph。将它们与 Time Profiler 和 Hangs/Hitches 关联分析。
在调试版本中添加 Self._printChanges(),以记录触发视图更新的属性:
var body: some View {
#if DEBUG
let _ = Self._printChanges() // "MyView: @self, _count changed."
#endif
Text("Count: \(count)")
}
完整的性能分析工作流请参阅 references/optimizing-swiftui-performance-instruments.md。
标识与生命周期
标识决定视图的生命周期和状态。在重复内容中使用稳定的模型 ID,并仅在有意重置时更改 .id(_:)。在经过性能分析的热点行中,优先使用 @ViewBuilder 或泛型组合,而不是 AnyView。如果证据表明状态频繁变化或视图重新创建代价高昂,应将根条件分支视为可疑对象,而非直接认定为缺陷。
Text(title)
.foregroundStyle(isHighlighted ? .yellow : .primary)
ForEach(items) { item in
Row(item: item).id(item.stableID)
}
延迟加载模式
只有在性能分析表明急切构建、布局或更新工作影响显著时,才使用延迟容器;不存在适用于所有情况的项目数阈值。网格/列表的构建方式请转到 swiftui-layout-components。
注意事项:
- 屏幕外的视图会从延迟堆栈中移除。SwiftUI 可能会短暂保留它们,随后删除这些视图及其视图局部状态。
- 如果重要的行状态必须在滚动离开后保留,请将其保存在行视图之外。
- 由于预取,
onAppear之前可能已经发生主体和布局工作。不要把onAppear作为行渲染所需数据的唯一设置点。 - 将
onAppear和onDisappear视为可见性信号,而非生命周期保证。 - 在
ForEach之前筛选数据;避免使用if分支让每个元素生成零行或一行。 - 将每个
ForEach元素的顶层子视图数量保持为固定值。必要时,使用稳定容器包装行内容。调试列表/表格慢路径时使用-LogForEachSlowPath YES。 - 避免假设内容尺寸或内容偏移为绝对值;延迟堆栈会估算屏幕外的尺寸。
- 避免在延迟行中形成几何反馈循环。优先采用稳定尺寸、布局原语或自定义
Layout,再将几何变化反馈到行状态中。
状态与观察优化
Observation 会跟踪视图求值期间读取的属性。通过传递范围更窄的派生值,或将读取移入职责明确的子视图,减少影响范围。
// Split reads into child views so each tracks only what it renders.
struct ProfileView: View {
let model: ProfileModel
var body: some View {
VStack {
NameRow(model: model) // only tracks name
EmailRow(model: model) // only tracks email
AvatarView(model: model) // only tracks avatar
SettingsForm(model: model) // only tracks settings
}
}
}
廉价的计算值可以继续作为派生值;昂贵的转换则需要明确的所有者、输入集和刷新触发条件。不要把添加视图模型当作性能优化的例行做法——先进行测量,并将通用状态设计问题转到 swiftui-patterns。
常见错误
- 分析 Debug 构建。 Debug 构建包含额外的运行时检查,并会禁用优化,因此会产生误导性的性能数据。请在真实设备上分析 Release 构建。
- 只需观察一个属性,却观察整个模型。 将大型
@Observable模型拆分为职责明确的小型模型,或使用计算属性/闭包来缩小观察范围。 - 在 ScrollView 项中使用几何反馈。 GeometryReader 或频繁变化的几何状态可能导致重复布局。优先使用稳定的尺寸、自定义布局,或将
.onGeometryChange(iOS 16+) 限定在较小范围内,并设置阈值。 - 在
body中调用DateFormatter()或NumberFormatter()。 创建这些对象的开销很大。将它们设为静态,或移到视图之外。 - 为不可比较状态添加动画。 如果 SwiftUI 无法判断相等性,就会在每一帧重新绘制。让状态遵循
Equatable,然后对简单的值绑定更改使用.animation(_:value:),或对范围更窄的修饰符作用域隐式动画使用.animation(_:body:)。 - 使用没有标识符的大型扁平
List。 使用id:或让项目遵循Identifiable,使 SwiftUI 能够高效比较差异,而不是重建整个列表。 - 不必要地使用
@State包装对象。 为了使用@State而将简单值类型包装在类中,会破坏值语义。对结构体使用普通的@State。 - 用同步 I/O 阻塞
MainActor。 文件读取、大型负载的 JSON 解析和图像解码都应在主 actor 之外进行。优先使用非隔离的异步辅助方法或专用 actor;仅在有意打破 actor 继承并自行处理取消的情况下使用Task.detached。
审查清单
-
body中没有分配DateFormatter/NumberFormatter - 大型列表使用遵循
Identifiable的项目或显式的id: -
@Observable模型仅公开视图实际读取的属性 - 耗时计算在
MainActor之外进行(图像处理、解析) - 延迟行具有稳定标识、固定的顶层行结构,并使用预先筛选的数据
- 滚动行中的几何变化设有阈值,且不会影响范围过大的状态
- 行渲染不以
onAppear作为唯一的设置时机 - 隐式动画对值绑定更改使用
.animation(_:value:),或对范围更窄的修饰符作用域使用.animation(_:body:) - 主线程上没有同步网络/文件 I/O
- 在真实设备上的 Release 构建中完成性能分析
-
@State不用作未明确说明用途的缓存;每个派生值都有明确的所有者和刷新触发条件 - 仅当比较的开销低于重新计算,且输入具有稳定的值语义时才使用
equatable() - 调查结论能区分基于代码的假设与基于跟踪数据的证据
-
@Observable视图模型隔离于@MainActor;跨并发边界的类型遵循Sendable
参考资料
- 揭秘 SwiftUI 性能(WWDC23):references/demystify-swiftui-performance-wwdc23.md
- 使用 Instruments 优化 SwiftUI 性能:references/optimizing-swiftui-performance-instruments.md
- 了解应用中的卡顿:references/understanding-hangs-in-your-app.md
- 了解并改进 SwiftUI 性能:references/understanding-improving-swiftui-performance.md
- WWDC 演讲文字稿来源:references/wwdc-session-sources.md