文章

在您的 App 中管理模型数据

在您的 app 中的数据模型和视图之间建立联系。

概览

您通常会在您的 app 中,使用与用户界面和其他逻辑分离的数据模型来存储和处理数据。这种分离可促进模块化、提升可测试性,并更加便于推断您的 app 的运行方式。

此前,您使用一个视图控制器在模型和用户界面之间来回移动数据,而此同步过程的大部分工作都可由 SwiftUI 为您处理。若要在数据发生变化时更新视图,您需要使您的数据模型类成为可观察对象、发布它们的属性并使用特殊属性声明它们的实例。为确保用户驱动的数据更改回流到模型中,您需要将用户界面控件与模型属性进行绑定。这些功能一起帮助您维护数据的单一来源。

将模型数据设为可观察

要让模型中的数据变化对 SwiftUI 可见,请为模型类采用 ObservableObject (英文) 协议。例如,您可以创建一个 Book 类作为可观察对象:


class Book: ObservableObject {
}

系统会自动推断该类的 ObjectWillChangePublisher (英文) 关联类型,并合成必需的 objectWillChange (英文) 方法来发出已发布属性的更改值。若要发布属性,请将 Published (英文) 属性添加到属性的声明:


class Book: ObservableObject {
    @Published var title = "Great Expectations"
}

请避免已发布属性的不必要开销。仅发布既能更改又对用户界面很重要的属性。例如,Book 类可能有一个在初始化后从不更改的 identifier 属性:


class Book: ObservableObject {
    @Published var title = "Great Expectations"


    let identifier = UUID() // A unique identifier that never changes.
}

您仍可以在用户界面中显示该标识符,但由于它未发布,SwiftUI 知道不必观察该特定数据的变化情况。

监测可观察对象的变化

若要指示 SwiftUI 监测可观察对象,请将 ObservedObject (英文) 属性添加到属性的声明:


struct BookView: View {
    @ObservedObject var book: Book
    
    var body: some View {
        Text(book.title)
    }
}

您可以将所观察对象的各个属性传递到子视图,如上所示。当数据发生变化时,例如,当您从磁盘载入新数据时,SwiftUI 会更新所有受影响的视图。您还可以将整个可观察对象传递到子视图,并在视图层次结构的各个层级之间共享模型对象:


struct BookView: View {
    @ObservedObject var book: Book
    
    var body: some View {
        BookEditView(book: book)
    }
}


struct BookEditView: View {
    @ObservedObject var book: Book


    // ...
}

实例化视图中的模型对象

SwiftUI 可能会随时创建或重新创建一个视图,因此很重要的一点是:用给定的一组输入来初始化视图时,要能够始终产生相同的视图。因此,在视图中创建被观察对象不是很安全的做法。您应借助 SwiftUI 提供的 StateObject (英文) 属性来实现这一用途。您可以通过以下方式在视图中安全地创建一个 Book 实例:


struct LibraryView: View {
    @StateObject var book = Book()
    
    var body: some View {
        BookView(book: book)
    }
}

状态对象的行为就像被观察对象一样,唯一不同的是,无论 SwiftUI 重新创建了一个视图多少次,它都能够创建并管理给定视图实例的单一对象实例。您可以在本地使用该对象,或将状态对象传递到另一个视图的被观察对象属性,如上述示例所示。

尽管 SwiftUI 不会在视图中重新创建状态对象,但它却会为每个视图实例创建一个不同的对象实例。例如,以下代码中的每个 LibraryView 都将获得一个唯一的 Book 实例:


VStack {
    LibraryView()
    LibraryView()
}

您还可以在顶层 App (英文) 实例或您 app 的一个 Scene (英文) 实例中创建一个状态对象。例如,如果您为一个图书阅读器 app 定义一个名为 Library 的可观察对象来保留一系列图书,您可以在该 app 的顶层结构中创建一个资料库实例:


@main
struct BookReader: App {
    @StateObject var library = Library()


    // ...
}

在整个 App 中共享对象

如果您有一个想在整个 app 中使用的数据模型对象,但是,您不想将它传递到层次结构的多个层级,您可以改用 environmentObject(_:) (英文) 视图修饰符,将该对象放置在环境中:


@main
struct BookReader: App {
    @StateObject var library = Library()
    
    var body: some Scene {
        WindowGroup {
            LibraryView()
                .environmentObject(library)
        }
    }
}

然后,应用了该修饰符的视图的任何子代视图均可通过使用 EnvironmentObject (英文) 属性声明一个属性来访问该数据模型实例:


struct LibraryView: View {
    @EnvironmentObject var library: Library
    
    // ...
}

如果您使用环境对象,可以将它添加到 app 层次结构顶部的视图,如上所示。或者,您可以将它添加到视图层次结构中某个子树的根视图。无论采用哪种方式,都还要记得将其添加到使用此对象或子代使用此对象的任何视图的预览提供程序:


struct LibraryView_Previews: PreviewProvider {
    static var previews: some View {
        LibraryView()
            .environmentObject(Library())
    }
}

使用绑定创建双向连接

如果您允许用户在用户界面中更改数据,请使用与相应属性的绑定。这确保更新会自动回流到数据模型中。您可以通过给对象的名称添加美元符号 ($) 前缀,获得与被观察对象、状态对象或环境对象属性的绑定。例如,如果您让用户通过向 BookEditView 添加一个 TextField (英文) 来编辑某图书的书名,应为文本栏提供与该图书的 title 属性的绑定:


struct BookEditView: View {
    @ObservedObject var book: Book
    
    var body: some View {
        TextField("Title", text: $book.title)
    }
}

该绑定将视图元素与底层模型进行关联,以便用户可直接更改模型数据。

另请参阅

模型数据