SwiftUI 性能优化 — 流畅的 UI,减少重新计算
Source: Dev.to
SwiftUI 是 快速 的——但前提是你要正确使用它。
随着时间的推移,你会遇到:
- 卡顿的滚动性能
- 卡顿的动画
- 列表缓慢
- 视图刷新过于频繁
- 不必要的重新计算
- 异步工作阻塞 UI
- 图片导致的内存激增
- 120 Hz 动画掉帧
本指南展示了我在所有应用中遵循的 真实场景性能规则。这些并非理论,而是解决你在构建完整 SwiftUI 应用时实际遇到的问题。让你的 UI 如黄油般顺滑。🧈⚡
1. 将重量级视图拆分为更小的子视图
当任何 @State/@ObservedObject 变化时,SwiftUI 会重新渲染 整个视图。
不佳:
VStack {
Header()
ExpensiveList(items: items) // ← 巨大的视图
}
.onChange(of: searchQuery) {
// 整个视图重新计算
}
更好:
VStack {
Header()
ExpensiveList(items: items) // 已隔离
}
最佳:
struct BigScreen: View {
var body: some View {
Header()
BodyContent() // 隔离的子视图防止额外的重新计算
}
}
小而可复用的组件 = 巨大的性能提升。
2. 正确使用 @StateObject 与 @observable
对不应 重新初始化 的对象使用 @StateObject:
@StateObject var viewModel = HomeViewModel()
对轻量模型使用 @observable:
@Observable
class HomeViewModel { … }
对简单值使用 @State(不要在 @State 中存放复杂结构体)。绝不要在 @State 中存放重量级对象。
3. 将异步工作放在主线程之外
错误示例:
func load() async {
let data = try? API.fetch() // 速度慢
self.items = data // UI 冻结
}
正确示例:
func load() async {
let data = try? await Task.detached { await API.fetch() }.value
await MainActor.run { self.items = data }
}
永远不要阻塞主线程——SwiftUI 依赖它。
4. 对重量级绘制使用 .drawingGroup()
如果你渲染渐变、模糊层、大符号或复杂遮罩,开销会很快升高。
MyComplexShape()
.drawingGroup()
这会强制 GPU 渲染通道 → 大幅提升速度。
5. 优化图片(最常见的卡顿来源)
使用已调整大小的缩略图:
Image(uiImage: image.resized(to: 200))
避免在 ScrollView 中加载大图。推荐使用:
.resizable().scaledToFit().interpolation(.medium)- 异步加载 + 缓存
对于远程图片,使用 URLCache 或类似 Nuke 的库。
6. 避免在 ScrollView 中进行重量级布局工作
常见错误:
ScrollView {
ForEach(items) { item in
ExpensiveLayout(item: item)
}
}
在滚动时进行重量级布局会导致卡顿。
解决方案:
- 减少修饰符
- 缓存计算值
- 将子视图拆分为隔离的组件
7. 在需要时优先使用 LazyVGrid / LazyVStack 而非 List
List 很棒——但并非在所有情况下都适用。
使用 LazyVStack 的场景:
- 需要自定义动画
- 拥有大型组合布局
- 行内包含复杂容器
使用 List 的场景:
- 行结构简单
- 想要原生单元格优化
8. 避免使用 .id(UUID()) 重新计算视图
MyView()
.id(UUID()) // BAD – 销毁身份
这会在每帧强制完整视图重新加载。仅在需要受控重置时使用 .id(...)。
9. 计算属性应保持快速
不佳:
var filtered: [Item] {
hugeArray.filter { … } // 开销大
}
每次渲染都会重新计算。
更好:
@State private var filtered: [Item] = []
在需要时更新:
.onChange(of: searchQuery) { _ in
filtered = hugeArray.filter { … }
}
10. 使用 Transaction 控制动画开销
默认动画可能卡顿。通过以下方式平滑它们:
withTransaction(Transaction(animation: .snappy)) {
isOpen.toggle()
}
自定义动画事务可以减少布局跳动。
11. 在批量更新期间关闭动画
withAnimation(.none) {
items = newItems
}
防止在大列表操作时出现卡顿。
12. 使用 Instruments(是的,它对 SwiftUI 有效)
使用以下工具进行分析:
- SwiftUI “Dirty Views”
- Memory Graph
- Time Profiler
- Allocation Tracking
- FPS 掉帧
90 % 的卡顿来源于:
- 巨大视图
- 昂贵的初始化
- 图片
- 主线程阻塞
✔️ 最终性能检查清单
发布前,请确保:
- 没有主线程 API 调用
- 没有昂贵的计算属性
-
ScrollView内没有布局抖动 - 图片已调整大小或已缓存
- 重量级视图已拆分为组件
- ViewModel 使用
@StateObject或 @observable - 动画使用
.snappy或.spring并已隔离 - 列表在需要时使用惰性容器