No history yet

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 类的 bookslocation 属性都自动具备了可观察性。当 SwiftUI 视图访问这些属性时,Observation 框架会记录下这些访问。之后,只有当被访问过的属性发生变化时,相关的视图才会被重新渲染。例如,如果一个视图只显示 location,那么调用 checkOutBook 方法修改 books 数组将不会导致该视图刷新。

Observation 框架从两方面改善了可观察对象在 SwiftUI 中的性能表现:通过观察视图中的可观察属性而不是可观察对象,可以减少大量无效的视图更新。相较于 Combine 的发布者-订阅者模式,Observation 的回调机制更加高效。

从 ObservableObject 迁移

将现有代码从 ObservableObject 迁移到 @Observable 非常直接。主要步骤包括:

  1. class YourClass: ObservableObject 替换为 @Observable class YourClass
  2. 移除类中所有的 @Published 属性包装器。
  3. 在视图中,将 @StateObject 替换为 @State 来实例化和管理 @Observable 对象的生命周期。
  4. 对于传递给子视图的对象,可以直接作为常规属性传递,无需使用 @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: ViewModellet 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 数组中的某个 BookisAvailable 状态改变,则只有对应的 BookRowList 会更新。整个 LibraryViewbody 不会因为不相关的属性变化而重新计算,这就是 Observation 框架带来的性能优势。

Quiz Questions 1/4

在 Swift 的 Observation 框架中,使用哪个宏来使一个类成为可观察对象?

Quiz Questions 2/4

与传统的 ObservableObject 相比,@Observable 宏提供的主要性能优势是什么?

Observation 框架是 SwiftUI 未来的方向,它提供了更简洁的语法和更优的性能。掌握它对于构建现代化、高效的 SwiftUI 应用至关重要。