swiftui-debugging
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftUI Performance Debugging
SwiftUI性能调试
Systematic guide for diagnosing and fixing SwiftUI performance problems: unnecessary view re-evaluations, identity issues, expensive body computations, and lazy loading mistakes.
一份系统化的指南,用于诊断和修复SwiftUI性能问题:不必要的视图重评估、标识问题、耗时的body计算以及懒加载错误。
When This Skill Activates
何时启用此技能
Use this skill when the user:
- Reports slow or janky SwiftUI views
- Sees excessive view re-renders or body re-evaluations
- Asks about or view debugging
Self._printChanges() - Has scrolling performance issues with lists or grids
- Asks why a view keeps updating when nothing changed
- Mentions or
@Observableperformance differencesObservableObject - Wants to understand SwiftUI view identity or diffing
- Uses and asks about performance implications
AnyView - Has a hang or stutter traced to SwiftUI rendering
当用户出现以下情况时使用此技能:
- 反馈SwiftUI视图运行缓慢或卡顿
- 发现视图过度重渲染或body重复评估
- 询问或视图调试相关内容
Self._printChanges() - 列表或网格存在滚动性能问题
- 疑惑为何视图在无变更时持续更新
- 提及或
@Observable的性能差异ObservableObject - 想要了解SwiftUI视图标识或差异对比机制
- 使用并询问其性能影响
AnyView - 出现可追溯到SwiftUI渲染的卡顿或停滞
Decision Tree
决策树
What SwiftUI performance problem are you seeing?
|
+- Views re-render when they should not
| +- Read body-reevaluation.md
| +- Self._printChanges() to identify which property changed
| +- @Observable vs ObservableObject observation differences
| +- Splitting views to narrow observation scope
|
+- Scrolling is slow / choppy (lists, grids)
| +- Read lazy-loading.md
| +- VStack vs LazyVStack, ForEach without lazy container
| +- List prefetching, grid cell reuse
|
+- Views lose state unexpectedly / animate when they should not
| +- Read view-identity.md
| +- Structural vs explicit identity
| +- .id() misuse, conditional view branching
|
+- Known pitfall (AnyView, DateFormatter in body, etc.)
| +- Read common-pitfalls.md
| +- AnyView type erasure, object creation in body
| +- Over-observation, expensive computations
|
+- General "my SwiftUI app is slow" (unknown cause)
| +- Start with body-reevaluation.md, then common-pitfalls.md
| +- Use Instruments SwiftUI template (see Debugging Tools below)你遇到的SwiftUI性能问题是什么?
|
+- 视图在不应重渲染时发生重渲染
| +- 阅读body-reevaluation.md
| +- 使用Self._printChanges()确定哪个属性发生了变更
| +- @Observable与ObservableObject的观察差异
| +- 拆分视图以缩小观察范围
|
+- 滚动缓慢/卡顿(列表、网格)
| +- 阅读lazy-loading.md
| +- VStack与LazyVStack对比,无懒加载容器的ForEach
| +- List预加载、网格单元格复用
|
+- 视图意外丢失状态/不应动画时出现动画
| +- 阅读view-identity.md
| +- 结构性标识与显式标识
| +- .id()误用、条件视图分支
|
+- 已知陷阱(AnyView、body中的DateFormatter等)
| +- 阅读common-pitfalls.md
| +- AnyView类型擦除、body中的对象创建
| +- 过度观察、耗时计算
|
+- 通用问题“我的SwiftUI应用运行缓慢”(原因未知)
| +- 从body-reevaluation.md开始,然后查看common-pitfalls.md
| +- 使用Instruments的SwiftUI模板(见下方调试工具)API Availability
API可用性
| API / Technique | Minimum Version | Reference |
|---|---|---|
| iOS 15 | body-reevaluation.md |
| iOS 17 / macOS 14 | body-reevaluation.md |
| iOS 13 | body-reevaluation.md |
| iOS 14 | lazy-loading.md |
| iOS 14 | lazy-loading.md |
| iOS 13 | view-identity.md |
| Instruments SwiftUI template | Xcode 14+ | SKILL.md |
| iOS 12 | SKILL.md |
| API / 技术 | 最低版本 | 参考 |
|---|---|---|
| iOS 15 | body-reevaluation.md |
| iOS 17 / macOS 14 | body-reevaluation.md |
| iOS 13 | body-reevaluation.md |
| iOS 14 | lazy-loading.md |
| iOS 14 | lazy-loading.md |
| iOS 13 | view-identity.md |
| Instruments SwiftUI模板 | Xcode 14+ | SKILL.md |
| iOS 12 | SKILL.md |
Top 5 Mistakes -- Quick Reference
五大常见错误——快速参考
| # | Mistake | Fix | Details |
|---|---|---|---|
| 1 | Large | Wrap in | lazy-loading.md |
| 2 | Using | Use | common-pitfalls.md |
| 3 | Creating objects in | Use | common-pitfalls.md |
| 4 | Observing entire model when only one property is needed | Split into smaller | body-reevaluation.md |
| 5 | Unstable | Use stable identifiers (database IDs, UUIDs), never array indices or random values | view-identity.md |
| # | 错误 | 修复方案 | 详情 |
|---|---|---|---|
| 1 | 在 | 包裹在 | lazy-loading.md |
| 2 | 使用 | 使用 | common-pitfalls.md |
| 3 | 在 | 使用 | common-pitfalls.md |
| 4 | 观察整个模型但仅需其中一个属性 | 拆分为更小的 | body-reevaluation.md |
| 5 | 不稳定的 | 使用稳定标识符(数据库ID、UUID),切勿使用数组索引或随机值 | view-identity.md |
Debugging Tools
调试工具
Self._printChanges()
Self._printChanges()
Add to any view body to see what triggered re-evaluation:
swift
var body: some View {
let _ = Self._printChanges()
// ... view content
}Output reads:
See body-reevaluation.md for full interpretation guide.
ViewName: @self, @identity, _propertyName changed.添加到任意视图的body中,查看触发重评估的原因:
swift
var body: some View {
let _ = Self._printChanges()
// ... 视图内容
}输出示例:
查看body-reevaluation.md获取完整解读指南。
ViewName: @self, @identity, _propertyName changed.Instruments SwiftUI Template
Instruments SwiftUI模板
- Xcode > Product > Profile (Cmd+I)
- Choose SwiftUI template (includes View Body, View Properties, Core Animation Commits)
- Record, reproduce the slow interaction, stop
- View Body lane shows which views had their body evaluated and how often
- View Properties lane shows which properties changed
- Xcode > Product > Profile(快捷键Cmd+I)
- 选择SwiftUI模板(包含View Body、View Properties、Core Animation Commits)
- 开始录制,复现缓慢交互后停止
- View Body轨道显示哪些视图的body被评估以及评估频率
- View Properties轨道显示哪些属性发生了变更
os_signpost for Custom Measurement
os_signpost自定义测量
swift
import os
private let perfLog = OSLog(subsystem: "com.app.perf", category: "SwiftUI")
var body: some View {
let _ = os_signpost(.event, log: perfLog, name: "MyView.body")
// ... view content
}View in Instruments with the os_signpost instrument to count body evaluations per second.
swift
import os
private let perfLog = OSLog(subsystem: "com.app.perf", category: "SwiftUI")
var body: some View {
let _ = os_signpost(.event, log: perfLog, name: "MyView.body")
// ... 视图内容
}在Instruments中使用os_signpost工具查看每秒的body评估次数。
Review Checklist
检查清单
View Identity
视图标识
- No unstable values (random, Date(), array index on mutable arrays)
.id() - Conditional branches (/
if) do not cause unnecessary view destructionelse - uses stable, unique identifiers from the model
ForEach
- 无不稳定的值(随机值、Date()、可变数组的索引)
.id() - 条件分支(/
if)不会导致不必要的视图销毁else - 使用模型中稳定且唯一的标识符
ForEach
Body Re-evaluation
Body重评估
- Views observe only the properties they actually use
- classes preferred over
@Observable(iOS 17+)ObservableObject - No unnecessary changes that trigger body re-evaluation
@State - Large views split into smaller subviews to narrow observation scope
- 视图仅观察实际使用的属性
- iOS 17+优先使用类而非
@ObservableObservableObject - 无触发body重评估的不必要变更
@State - 大型视图拆分为更小的子视图以缩小观察范围
Lazy Loading
懒加载
- Large collections use /
LazyVStack, notLazyHStack/VStackHStack - or lazy stack used for 50+ items
List - No on children inside lazy containers (defeats laziness)
.frame(maxHeight: .infinity)
- 大型集合使用/
LazyVStack而非LazyHStack/VStackHStack - 50+条目的内容使用或懒加载栈
List - 懒加载容器内的子视图未使用(会破坏懒加载机制)
.frame(maxHeight: .infinity)
Common Pitfalls
常见陷阱
- No type erasure (use
AnyViewor@ViewBuilder)Group - No object allocation in (
body,DateFormatter, view models)NSPredicate - Expensive computations moved to background with or
task { }Task.detached - Images use or
AsyncImagewith proper sizing, not raw.resizable()decoding in bodyUIImage
- 未使用进行类型擦除(改用
AnyView或@ViewBuilder)Group - body中无对象分配(、
DateFormatter、视图模型)NSPredicate - 耗时计算通过或
task { }移至后台Task.detached - 图片使用或
AsyncImage并设置合适尺寸,未在body中直接解码原始.resizable()UIImage
Reference Files
参考文件
| File | Content |
|---|---|
| view-identity.md | Structural vs explicit identity, |
| body-reevaluation.md | What triggers body, |
| lazy-loading.md | Lazy vs eager containers, |
| common-pitfalls.md | |
| ../profiling/SKILL.md | General Instruments profiling (Time Profiler, Memory, Energy) |
| 文件 | 内容 |
|---|---|
| view-identity.md | 结构性标识与显式标识、 |
| body-reevaluation.md | 触发body重评估的原因、 |
| lazy-loading.md | 懒加载与非懒加载容器、 |
| common-pitfalls.md | |
| ../profiling/SKILL.md | 通用Instruments分析(时间分析器、内存、能耗) |