@ObservedObject 是SwiftUI中的一个属性包装器,允许View观察在层次结构其他位置创建的ObservableObject的变化。与@StateObject不同,@ObservedObject不会创建对象—它只订阅其objectWillChange发布者,并在@Published属性更新时重新绘制View。这使得@ObservedObject成为通过初始化器从父级接收数据的子View的正确选择。根据Paul Hudson的文章—Hacking with Swift (2025),典型的SwiftUI应用程序架构构建如下:根View使用@StateObject创建view model,所有子View通过@ObservedObject接收它,从而确保单一数据源而无需重复数据。
主要内容
@ObservedObject 是一个属性包装器,它将View订阅到ObservableObject的变化。当标记有@ObservedObject的对象更改其任何用@Published声明的属性时,SwiftUI会自动重新绘制View。@ObservedObject不会创建对象—它只在现有的ObservableObject实例和需要对其变化做出反应的View之间建立连接。
@ObservedObject 和 @StateObject 之间的关键区别—是所有权。@ObservedObject假定对象是在View层次结构中更高的位置创建和存储的。子View通过初始化器获取此对象的引用并简单地观察它。如果子View被重新创建,它将从父级获得相同的引用—数据不会丢失。
根据Apple Developer Documentation — SwiftUI (2025),@ObservedObject从iOS 13起可用,这使其成为支持旧版iOS的项目中观察ObservableObject的唯一选项。在iOS 14+中,创建对象更推荐使用@StateObject,但@ObservedObject仍然适用于传递现有对象。
@ObservedObject 的机制基于Combine框架中的ObservableObject协议。每个符合ObservableObject的类都会自动获得一个objectWillChange发布者,该发布者在任何@Published属性更改之前发送信号。SwiftUI通过@ObservedObject订阅此发布者,并在收到信号后将View标记为需要重新绘制。
class TaskViewModel: ObservableObject {
@Published var tasks: [Task] = []
@Published var isLoading = false
func loadTasks() async {
isLoading = true
// 获取数据
isLoading = false
}
}
struct TaskListView: View {
@ObservedObject var viewModel: TaskViewModel
var body: some View {
List(viewModel.tasks) { task in
Text(task.title)
}
.task { await viewModel.loadTasks() }
}
}
当父View通过初始化器将viewModel传递给TaskListView时,SwiftUI在对象和View之间建立连接。当tasks数组或isLoading标志更改时,SwiftUI重新绘制TaskListView。对象保持不变—它通过@StateObject存储在父View中。
@ObservedObject 和 @StateObject 之间的区别—是观察者和所有者之间的区别。@StateObject创建对象并管理其生命周期。@ObservedObject只观察在其他地方创建和存储的对象。它们之间的选择由View对数据的责任决定。
| 场景 | 建议 | 原因 |
|---|---|---|
| View创建数据 | @StateObject | View拥有对象并负责其生命周期 |
| View接收数据 | @ObservedObject | View只观察,对象存在于父级中 |
| 支持iOS 13 | @ObservedObject | @StateObject不可用,使用@ObservedObject并在手动管理 |
| 可复用组件 | @ObservedObject | 组件不应创建数据—它从外部接收数据 |
主要规则:如果View创建对象—@StateObject。如果View接收对象—@ObservedObject。违反此规则使用@ObservedObject创建对象会导致View重建时数据丢失。违反规则使用@StateObject接收对象会创建一个独立的重复实例,与父级无关。
@ObservedObject 的典型使用场景—任务列表,其中根View创建view model,列表单元格通过@ObservedObject接收它。每个单元格都可以调用view model的方法,更改会自动显示在整个列表中,因为所有单元格都观察相同的对象。
struct TaskRow: View {
@ObservedObject var viewModel: TaskViewModel
let task: Task
var body: some View {
HStack {
Text(task.title)
Spacer()
Button("完成") {
viewModel.completeTask(task)
}
}
}
}
struct TaskListContainer: View {
@StateObject var viewModel = TaskViewModel()
var body: some View {
List(viewModel.tasks) { task in
TaskRow(viewModel: viewModel, task: task)
}
}
}
在此示例中,TaskListContainer通过@StateObject创建viewModel,每个TaskRow通过@ObservedObject接收它。当用户在任何行中点击“Done”时,viewModel.completeTask更改published属性,所有观察此对象的View都会自动更新。
最常见的错误—在View内部使用@ObservedObject创建对象。当View重建时(例如在状态改变时),SwiftUI会创建一个新的ObservableObject实例,导致所有累积数据丢失。这个错误在NavigationStack中尤其痛苦,用户可能填写表单并在返回时丢失数据。
// ❌ 数据丢失:@ObservedObject不保留对象
struct FormView: View {
@ObservedObject var formVM = FormViewModel()
// 每次View重建时都会创建新的formVM!
}
// ✅ 正确:@StateObject保留对象
struct FormView: View {
@StateObject var formVM = FormViewModel()
// 对象在View生命周期内创建一次
}
如果子View通过@StateObject声明相同的ObservableObject,它会创建一个独立的副本。父对象中的更改将不会在子对象中可见,反之亦然。始终为从外部接收对象的子View使用@ObservedObject。
在现代SwiftUI中,有几种@ObservedObject的替代方案,各有其优势。选择取决于应用程序架构、iOS版本和特定的使用场景。
在@ObservedObject和@EnvironmentObject之间的选择—是风格和架构的问题。@ObservedObject通过初始化器显式显示View的依赖关系,使代码更可预测。@EnvironmentObject适用于深层层次结构,但隐藏依赖关系,可能会使调试变得困难。
常见问题
可以,如果对象是在SwiftUI之外创建和存储的—例如,在AppDelegate或单例中。在这种情况下,@ObservedObject只是订阅现有对象的更改。然而,对于在SwiftUI层次结构内创建的对象,始终需要某处更高位置的@StateObject。
最可能的原因—属性不是通过@Published更改的,或者不是对象本身更改,而是其内部结构在没有调用objectWillChange的情况下更改。对于集合,使用新副本的赋值:array.append()是不够的—需要通过array = array + [element]重新分配整个数组。
@ObservedObject本身不会造成显著负担。问题出现在频繁更改@Published属性时—每次更改都会触发所有观察View的重新绘制。为了优化,使用EquatableView,减少published属性的数量,避免不必要的更新。
@ObservedObject 观察整个ObservableObject类,并在其published属性发生任何更改时重新绘制View。@Binding 创建与特定值(String、Int、Bool)的双向连接,并允许读取和写入它。@Binding更轻量,不需要ObservableObject。
可以,这是标准模式。ObservableObject内部的@Published会自动与@ObservedObject集成。每个@Published属性都会向objectWillChange发布者添加一个观察者。当其中任何一个更改时,所有带有@ObservedObject的该对象的View都会重新绘制。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。