SwiftUI 渲染管线解析

发布: (2025年12月22日 GMT+8 09:32)
6 min read
原文: Dev.to

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 执行布局:

  1. 父视图提出一个尺寸。
  2. 子视图选择自己的尺寸。
  3. 父视图定位子视图。

布局会在每次失效后、动画期间、尺寸变化、旋转以及列表更新时进行。

🧨 布局性能杀手

  • 过度使用 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一起工作。

Back to Blog

相关文章

阅读更多 »

SwiftUI 中的模块化特性架构

🧩 1. 什么是 Feature Module?Feature Module 是一个自包含的单元,代表应用中的一个功能块:Home、Profile、Settings、Feed、Auth 等。

构建可复用的 SwiftUI 组件库

SwiftUI 让构建 UI 变得轻而易举——但构建在整个应用中外观一致的可复用组件却是另一项挑战。随着应用的增长,UI 复制……