SwiftUI高级分层架构与Observation实战
SwiftUI Observation Framework
Observation 框架核心
在 WWDC 2023 中,苹果公司推出了 Observation 框架,它作为 Swift 标准库的新成员,从根本上改变了 SwiftUI 的响应式编程模型。此框架利用 Swift 的宏(Macros)功能,提供了一种更高效、更精细化的方式来追踪数据变化并更新视图。
SwiftUI 5 使用全新的 Observation 框架取代 Combine,彻底改变了其反应式的基础。
与之前依赖于 Combine 框架的 ObservableObject 协议和 @Published 属性包装器不同,Observation 框架允许 SwiftUI 仅在视图实际访问(读取)的特定属性发生变化时才触发视图更新。这种“访问时追踪”(tracking-on-access)机制避免了因对象中任何 @Published 属性变化而导致整个视图无效的大量更新,从而显著提升了性能。
使用 @Observable 宏
要使一个类可被观察,只需在其定义前添加 @Observable 宏即可。这个宏会自动为类的所有存储属性添加观察能力,不再需要为每个属性单独使用 @Published 进行标记。
import Observation
// Book 模型,遵循 Identifiable 以便在列表中使用
struct Book: Identifiable {
let id = UUID()
var title: String
var isAvailable: Bool = true
}
// 使用 @Observable 宏使 Library 类可被观察
@Observable
class Library {
var books: [Book] = [
Book(title: "Foundation"),
Book(title: "Dune")
]
var location: String = "Main Street Branch"
func checkOutBook(id: UUID) {
if let index = books.firstIndex(where: { $0.id == id }) {
books[index].isAvailable = false
}
}
}
在上面的例子中,Library 类的 books 和 location 属性都自动具备了可观察性。当 SwiftUI 视图访问这些属性时,Observation 框架会记录下这些访问。之后,只有当被访问过的属性发生变化时,相关的视图才会被重新渲染。例如,如果一个视图只显示 location,那么调用 checkOutBook 方法修改 books 数组将不会导致该视图刷新。
Observation 框架从两方面改善了可观察对象在 SwiftUI 中的性能表现:通过观察视图中的可观察属性而不是可观察对象,可以减少大量无效的视图更新。相较于 Combine 的发布者-订阅者模式,Observation 的回调机制更加高效。
从 ObservableObject 迁移
将现有代码从 ObservableObject 迁移到 @Observable 非常直接。主要步骤包括:
- 将
class YourClass: ObservableObject替换为@Observable class YourClass。 - 移除类中所有的
@Published属性包装器。 - 在视图中,将
@StateObject替换为@State来实例化和管理@Observable对象的生命周期。 - 对于传递给子视图的对象,可以直接作为常规属性传递,无需使用
@ObservedObject。
| 旧方法 (Combine) | 新方法 (Observation) |
|---|---|
class ViewModel: ObservableObject | @Observable class ViewModel |
@Published var name: String = "" | var name: String = "" |
@StateObject private var vm = ViewModel() | @State private var vm = ViewModel() |
@ObservedObject var vm: ViewModel | let vm: ViewModel |
EnvironmentObject var vm: ViewModel | @Environment(ViewModel.self) var vm |
一个关键的区别在于所有权和实例化。过去,@StateObject 用于创建并持有 ObservableObject 的实例,确保其生命周期与视图绑定。对于 @Observable 对象,我们现在使用 @State 来达到同样的目的,这强调了该对象是视图状态的一部分。当通过环境传递时,新的 @Environment 宏取代了 @EnvironmentObject。
实际应用与考量
在 SwiftUI 视图中使用 @Observable 对象时,视图会自动订阅其访问的属性。这意味着你不再需要手动管理订阅或考虑 objectWillChange 发布者。
让我们看一个视图如何使用上面定义的 Library 类:
struct LibraryView: View {
@State private var library = Library()
var body: some View {
NavigationStack {
VStack {
// 这个 Text 视图只依赖 'location' 属性
Text("Location: \(library.location)")
.font(.headline)
// 这个 List 只依赖 'books' 属性
List(library.books) { book in
BookRow(book: book)
}
}
.navigationTitle("My Library")
}
}
}
struct BookRow: View {
// book 是一个 let 常量,因为变化由其源头(Library)管理
let book: Book
var body: some View {
HStack {
Text(book.title)
Spacer()
Text(book.isAvailable ? "Available" : "Checked Out")
.foregroundStyle(book.isAvailable ? .green : .red)
}
}
}
在这个例子中,如果 library.location 发生变化,只有 Text 视图会更新。如果 library.books 数组中的某个 Book 的 isAvailable 状态改变,则只有对应的 BookRow 和 List 会更新。整个 LibraryView 的 body 不会因为不相关的属性变化而重新计算,这就是 Observation 框架带来的性能优势。
在 Swift 的 Observation 框架中,使用哪个宏来使一个类成为可观察对象?
与传统的 ObservableObject 相比,@Observable 宏提供的主要性能优势是什么?
Observation 框架是 SwiftUI 未来的方向,它提供了更简洁的语法和更优的性能。掌握它对于构建现代化、高效的 SwiftUI 应用至关重要。