SwiftUI 性能优化
问题
SwiftUI 的性能瓶颈在哪里?如何减少不必要的视图重建?
答案
View 重建原理
当 @State、@Binding 等状态变化时,SwiftUI 会重新调用相关 View 的 body。但调用 body ≠ 重新渲染——SwiftUI 内部会 Diff 新旧 View 树,只更新变化的部分。
性能问题出现在:body 计算过于复杂或被不必要地频繁调用。
常见优化手段
1. 拆分小视图
// ❌ 巨型 body
struct ContentView: View {
@State var count = 0
@State var name = ""
var body: some View {
VStack {
Text("Count: \(count)") // count 变化时,name 相关视图也重建
TextField("Name", text: $name)
// ... 100 行 UI 代码
}
}
}
// ✅ 拆分后,各子视图独立刷新
struct CounterSection: View {
@Binding var count: Int
var body: some View {
Text("Count: \(count)")
}
}
2. 使用 Equatable 避免无效更新
struct ExpensiveView: View, Equatable {
let data: SomeData
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.data.id == rhs.data.id // 只有 id 变化才更新
}
var body: some View {
// 复杂的渲染...
}
}
// 使用
ExpensiveView(data: item)
.equatable() // 启用 Equatable 检查
3. @Observable 精准追踪(iOS 17+)
@Observable
class ViewModel {
var title = "" // 只有用到 title 的视图才刷新
var subtitle = "" // 只有用到 subtitle 的视图才刷新
var unused = 0 // 没有被 body 读取,变化不会触发任何刷新
}
4. 避免在 body 中做复杂计算
// ❌
var body: some View {
let filtered = items.filter { $0.isActive }.sorted() // 每次重建都计算
List(filtered) { ... }
}
// ✅ 使用缓存
@State private var filtered: [Item] = []
var body: some View {
List(filtered) { ... }
.onChange(of: items) { _, newValue in
filtered = newValue.filter { $0.isActive }.sorted()
}
}
5. 列表优化
// ✅ 使用 LazyVStack/LazyVGrid
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemRow(item: item)
}
}
}
// ✅ id 参数确保正确 Diff
ForEach(items, id: \.uniqueID) { item in ... }
调试工具
// 打印 View body 被调用的时机
let _ = Self._printChanges()
// Instruments → SwiftUI → View Body
// 可以看到哪些 View 被频繁重建
常见面试问题
Q1: SwiftUI 列表性能和 UITableView 比怎么样?
答案:
List+LazyVStack:iOS 15+ 性能接近 UITableView(底层用 UICollectionView)- 复杂 Cell + 大数据量:UIKit 仍有优势(可以用
UIViewRepresentable桥接) - iOS 17+ 性能大幅改善,多数场景不需要回落到 UIKit
Q2: 如何定位 SwiftUI 性能问题?
答案:
Self._printChanges():打印 body 重建原因- Instruments → SwiftUI:分析 View 创建和 body 调用
- Instruments → Time Profiler:找到 CPU 热点
- 检查
@StateObjectvs@ObservedObject使用是否正确(避免意外重建)