在 SwiftUI 中,类似 .equatable() 这种“帮助 SwiftUI 少算、少重绘”的性能优化 modifier 或技巧,其实还有不少,但很多确实很少被广泛提起,尤其是日常业务开发中。下面列出一些实用程度高、但知道的人相对少的(2025–2026 年视角):
| 排序 | Modifier / 技巧 | 作用(简单说明) | 实用场景 / 收益程度 | 普及度(主观) | 出现版本(大致) |
|---|---|---|---|---|---|
| 1 | .equatable() |
值没变 → 不重算 body | List、LazyStack、复杂 cell | 中高 | iOS 13+ |
| 2 | .transaction { $0.animation = nil } |
局部禁用动画(最强“防抖”手段之一) | 频繁小改动但不想看到动画抖动 | 中 | iOS 13+ |
| 3 | .id() 写得很精准 |
强制 identity 变更,控制重绘/重用时机 | tab 切换、数据重置、避免状态残留 | 高 | iOS 13+ |
| 4 | .drawingGroup() |
把子树转成一张离屏纹理(GPU 加速) | 复杂形状、大量渐变、阴影、mask | 中高(但慎用) | iOS 13+ |
| 5 | .compositingGroup() |
把子视图合成一张图层,隔离重绘范围 | 大量叠加效果、opacity 动画 | 中 | iOS 13+ |
| 6 | .matchedGeometryEffect() + 条件 |
精确控制哪些 view 参与动画匹配(不是所有都匹配) | 列表 → 详情 动画优化 | 中高 | iOS 14+ |
| 7 | .selfSizingMask(alignment:) |
iOS 18+ 新增,让 mask 自己适应内容大小 | 动态气泡、标签、不规则遮罩 | 低(新) | iOS 18+ |
| 8 | .containerRelativeFrame(...) |
iOS 17+ 相对容器尺寸布局,取代很多 GeometryReader | 响应式卡片、百分比高度 | 中高(新) | iOS 17+ |
| 9 | @Entry macro + .containerValue |
iOS 18+ 新的向下传值方式,比 Environment 更精准、更快 | 深层传值、主题、滚动状态 | 低(很新) | iOS 18+ |
| 10 | .renderingMode(.template) + color 控制 |
让 SF Symbol / 图片只渲染模板色,减少图层 | 大量 icon 的列表 | 中 | iOS 13+ |
| 11 | .animation(.default, value: xxx) 的 value 参数 |
只在特定值变化时才触发动画(最被低估的写法) | 防抖动、精准动画控制 | 中高 | iOS 15+(加强) |
| 12 | .onChange 里用 withAnimation(nil) |
局部、无动画地响应状态变化 | 数据刷新但不想看到闪烁 | 中 | iOS 14+ |
| 13 | 自定义 View + @ViewBuilder 包裹 + @Equatable |
把一大坨 view 包成一个 struct 并实现 Equatable | 复杂子组件隔离 | 中 | iOS 13+ |
| 14 | .background(EmptyView().id(UUID())) 的 hack |
强制某些 view 不参与 identity 比较(极端情况) | 极少数 identity 冲突场景 | 很低 | — |
最推荐优先掌握的 5 个(性价比最高)
.equatable()→ 基础但最强.animation(..., value:)的 value 参数 → 控制动画触发的最精准方式.transaction { $0.animation = nil }→ 局部静音动画的神器.compositingGroup()+.drawingGroup()→ 处理复杂叠加/阴影时的 GPU 救命稻草- 精准使用
.id()→ 很多“莫名卡顿/状态错乱”其实是 identity 问题
极度冷门但有人用了会“起飞”的组合写法(2025–2026 真实案例)
// 超级防抖 cell(常见于聊天列表、feed)
struct MessageBubble: View, Equatable {
let message: Message
// 只比较真正影响 UI 的字段
static func == (lhs: Self, rhs: Self) -> Bool { ... }
var body: some View {
Text(message.text)
.padding()
.background(...)
.equatable()
.transaction { $0.animation = nil } // 防止打字时抖
.animation(.default.speed(1.8), value: message.text.count) // 只在字数变时微动画
}
}
// 复杂阴影 + 渐变区域用 drawingGroup 救性能
ZStack {
// 很多 layer...
}
.drawingGroup()
.compositingGroup()
你现在项目里最卡的地方大概是什么类型?(列表、复杂表单、大量动画、滚动卡顿、深层嵌套……)
告诉我具体场景,我可以更针对性地推荐最合适的组合。