每当被观察的状态变化时,SwiftUI 会重新运行 body,然后把新的视图树与旧的做 diff,只更新有差异的部分。性能问题来自让这个 diff 做了太多工作:不稳定的标识、过于宽泛的观察,以及昂贵的 body 计算。通过控制标识、收窄状态范围,以及保持 body 廉价来解决它。
SwiftUI 通过标识在各次渲染之间匹配视图。对于列表,这就是 id;如果 id 不稳定,SwiftUI 会丢弃并重建视图(丢失状态和动画),而不是更新它。
// BAD: array index as id — reorders/inserts break identity, full rebuild
ForEach(items.indices, id: \.self) { i in Row(items[i]) }
// GOOD: stable identity tied to the data
ForEach(items) { item in Row(item) } // items: Identifiable, stable id
一个 ObservableObject 在任何 @Published 属性变化时,会重新渲染每一个观察它的视图。拆分大的 model,或转向 Observation 框架(@Observable,iOS 17+),它会逐属性跟踪读取,因此视图只有在它实际用到的属性变化时才会重新渲染。
@Observable final class Feed { // iOS 17: fine-grained dependency tracking
var unreadCount = 0 // a view reading only .posts won't re-render on this
var posts: [Post] = []
}
body 经常运行,所以绝不要在其中内联做繁重工作:
var body: some View {
List(sortedItems) { ... } // BAD if sortedItems sorts on every render
}
// GOOD: compute derived data in the model, not in body
LazyVStack/List,这样屏幕外的行不会被构建。.equatable() 或 EquatableView,在稳定的子树上短路 diffing。AnyView 会擦除类型标识并破坏结构化 diffing——在热路径中避免使用它。在 120Hz 的 ProMotion 显示屏上,你每帧只有约 8ms;一个会排序大数组或重新渲染整个列表的 body 会丢帧并让滚动卡顿。资深面试会探究视图标识和观察范围,因为那正是真实 SwiftUI app 变慢的地方——也是少数几处精准修复就能恢复流畅的地方。
一个包含详细解答的 IT 面试题库——从初级到高级。
捐赠