SwiftUI 渲染管线解析
Source: Dev.to
🧠 大局观
每次 SwiftUI 更新都会经过相同的流水线:
State Change
↓
View Invalidation
↓
Body Recomputed
↓
Layout Pass
↓
Diffing
↓
Rendering
没有任何步骤会被跳过,也没有魔法。当某个阶段的工作量过大时,就会出现性能问题。
🔥 1. State Change (The Only Entry Point)
一切都始于 state。
示例
@State更改@StateObject/@ObservableObject属性更改@Binding更新- Environment 值更改
如果没有状态更改 → 不会重新渲染任何内容。这就是 SwiftUI 应用能够极其高效的原因。
⚠️ 常见错误
开发者常常认为 “body 重新计算得太多”。
这 不是问题所在。body 的重新计算成本很低;真正的开销来自 失效、布局和差异比较。
🧱 2. 视图失效
When state changes, SwiftUI:
- Marks affected views as dirty
- Schedules them for update
Important
- Only the affected subtrees invalidate, not the entire app.
- Global state everywhere → massive invalidation.
- Scoped state → minimal invalidation.
🔁 3. Body Recomposition (Cheap)
SwiftUI 重新执行:
var body: some View { … }
此步骤是:
- 快速
- 预期的
- 频繁的
SwiftUI 比较的是值,而不是对象。重新组合视图代价低;重新创建身份代价高。
🧠 关键洞察
SwiftUI 为频繁的 body 重新计算进行优化,而 不是 频繁的身份变化。
📐 4. 布局阶段
在 body 重新计算后,SwiftUI 执行布局:
- 父视图提出一个尺寸。
- 子视图选择自己的尺寸。
- 父视图定位子视图。
布局会在每次失效后、动画期间、尺寸变化、旋转以及列表更新时进行。
🧨 布局性能杀手
- 过度使用
GeometryReader(尤其在列表内部) - 深度嵌套的 Stack
- 每帧进行动态尺寸测量
- 视图尺寸依赖于异步数据并频繁变化
🔍 5. 差异化(关键步骤)
SwiftUI 使用以下方式比较 先前的视图树 与 新的视图树:
- 视图类型
- 位置
- 标识 (
id)
如果标识匹配:
- 状态被保留
- 视图就地更新
如果标识改变:
- 状态被销毁
- 视图被重新创建
- 动画被重置
- 任务重新启动
差异化是大多数“bug”产生的地方。
🆔 标识决定一切
快速(稳定的 ID)
ForEach(items, id: \.id) { item in
Row(item)
}
昂贵(每次更新都更改 ID)
ForEach(items) { item in
Row(item)
.id(UUID())
}
在每次更新时强制完整拆除会严重影响性能。
🎨 6. 渲染(GPU 阶段)
在完成 diff 之后,SwiftUI 会发出绘制指令,GPU 渲染出最终像素。渲染通常很快,除非你使用了以下情况:
- 大量模糊
- 分层材质
- 复杂遮罩
- 离屏渲染
- 过多阴影
渲染问题会表现为掉帧、卡顿的动画或滚动时的卡顿。
🧵 7. Animations in the Pipeline
Animations affect every stage:
- State changes → animated
- Layout interpolated
- Rendering interpolated
Animations do not skip layout or diffing; they amplify any inefficiencies, making poor architecture feel even worse.
⚙️ 8. 异步与渲染协调
不佳的写法
Task {
data = await load()
}
更佳的写法
@MainActor
func load() async {
isLoading = true
defer { isLoading = false }
data = await service.load()
}
原因
- 可预测的失效
- 单一渲染周期
- 避免级联更新
异步工作应批量处理状态更改,而不是逐个滴灌。
🧪 9. 为什么列表如此特殊
List:
- 积极复用视图
- 严重依赖标识
- 触发频繁的布局遍历
- 渲染离屏行
Performance depends on:
- 稳定的 ID
- 轻量级行
- 最小化布局工作
- 缓存的数据
Lists amplify every mistake in the rendering pipeline.
🧠 心理调试检查清单
当感觉某事运行缓慢时,问自己:
- 哪个状态发生了变化?
- 失效了多少?
- 标识(identity)改变了吗?
- 布局变得昂贵了吗?
- 动画是否放大了成本?
- 异步是否触发了多次更新?
你几乎总能定位问题。
🚀 最终思维模型
以层次思考,而非代码:
State
↓
Invalidation
↓
Body
↓
Layout
↓
Diffing
↓
Rendering
控制
- 状态范围
- 身份稳定性
- 布局复杂度
…而 SwiftUI 变得快速、可预测且可扩展。
🏁 最后思考
SwiftUI 性能并不是靠技巧;而是要尊重渲染管线。一旦你了解工作发生的地点、身份为何重要以及布局如何影响性能,你就不再与 SwiftUI 对抗——而是开始与它with一起工作。