在 SwiftUI 中观察对象和状态对象的区别是什么

如果我在 SwiftUI 中有一个 ObservableObject,我可以把它称为 @ObservedObject:

class ViewModel: ObservableObject {
@Published var someText = "Hello World!"
}


struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
    

var body: some View {
Text(viewModel.someText)
}
}

或者作为 @StateObject:

class ViewModel: ObservableObject {
@Published var someText = "Hello World!"
}


struct ContentView: View {
@StateObject var viewModel = ViewModel()


var body: some View {
Text(viewModel.someText)
}
}

但是这两者之间的实际区别是什么呢?有没有一个比另一个好的情况,或者是两个完全不同的情况?

19455 次浏览

@ Observer edObject

当视图创建自己的 @ObservedObject实例时,每次丢弃和重绘视图时都会重新创建该实例:

struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
}

相反,当重绘视图时,@State变量的值将为 留着

@ StateObject

@StateObject@ObservedObject@State的组合—— ViewModel的实例将被保留和重用,即使在一个视图被丢弃和重绘之后:

struct ContentView: View {
@StateObject var viewModel = ViewModel()
}

表演

尽管如果视图经常被迫重新创建一个重量级对象,那么 @ObservedObject可能会影响性能,但是当 @ObservedObject不复杂时,这并不重要。

何时使用@Observer 对象

现在似乎没有理由使用 @ObservedObject,那么应该在什么时候使用它呢?

对于任何可观察到的属性,您都应该使用@StateObject 在使用它的视图中初始化 外部创建并传递给使用它标记您的 具有@Observer 对象的属性。

注意,可能有太多的用例,有时可能是 渴望在您的视图中重新创建一个可观察的属性。在这种情况下,最好使用 @ObservedObject

有用连结:

尽管 Pawello2222的回答已经很好地解释了视图本身创建其视图模型时的差异,但是注意将视图模型注入到视图中时的差异仍然很重要。

当你将视图模型注入到视图中时,只要视图模型是一个引用类型,@ObservedObject@StateObject之间就没有区别,因为注入视图模型的对象也应该保存对视图模型的引用,因此当子视图被重绘时,视图模型不会被破坏。

class ViewModel: ObservableObject {}


struct ParentView: View {
@ObservedObject var viewModel = ViewModel()


var body: some View {
ChildView(viewModel: viewModel) // You inject the view model into the child view
}
}


// Even if `ChildView` is discarded/redrawn, `ViewModel` is kept in memory, since `ParentView` still holds a reference to it - `ViewModel` is only released and hence destroyed when `ParentView` is destroyed/redrawn.
struct ChildView: View {
@ObservedObject var viewModel: ViewModel
}

Apple 文档 确实解释了为什么用 ObservedObject初始化是 不安全

SwiftUI 可以在任何时候创建或重新创建视图,因此使用给定的输入集初始化视图始终导致相同的视图非常重要。因此,在视图内创建观察对象是不安全的。

解决方案是 StateObject

同时,文档向我们展示了如何在视图(或应用程序/场景)中创建数据模型,这些模型可以保留真实性,并将其传递给另一个视图。

struct LibraryView: View {
@StateObject var book = Book() // Hold on to the 1 truth
var body: some View {
BookView(book: book) // Pass it to another view
}
}


struct BookView: View {
@ObservedObject var book: Book // From external source
}

这里有一个例子来说明这种差异。

每次单击 Refresh按钮时,仅为 CountViewObserved从头重新创建 StateObjectClass。这意味着当这种情况发生时,它的 @Publishedcount属性将获得 0的默认值。

@StateObject@ObservedObject之间的区别是明显的。观察到的 StateObjectClass@StateObject版本保留了它的状态,因为它从未被取消过。@ObservedObject版本在重新创建时并不是这样的。因此,您应该使用 @StateObject作为 ObservableObject的所有者。

import SwiftUI


class StateObjectClass: ObservableObject {
enum ObserverType: String {
case stateObject
case observedObject
}
    

@Published var count = 0
let type: ObserverType
let id = UUID()
init(type: ObserverType) {
self.type = type
}
deinit {
print(#function, "type: \(type.rawValue) id: \(id)")
}
}


struct CountViewState: View {
@StateObject var state = StateObjectClass(type: .stateObject)
var body: some View {
VStack {
Text("@StateObject's count: \(state.count)")
Button("ADD 1"){
state.count += 1
}
}
}
}


struct CountViewObserved: View {
@ObservedObject var state = StateObjectClass(type: .observedObject)
var body: some View {
VStack {
Text("@ObservedObject's count: \(state.count)")
Button("Add 1") {
state.count += 1
}
}
}
}


struct ContentView: View {
@State private var count = 0
var body: some View {
VStack {


Text("Refresh CounterView's count: \(count)")
            

Button("Refresh") {
count += 1
}


CountViewState()
.padding()


CountViewObserved()
.padding()


}
}
}


@StateObject是给定视图的 国家,因此它的实例由 SwiftUI 在 body更新中保留。但是在预览中运行时不会保留它。

另一方面,@ObservedObject仅仅是给定 View被观察的物体,因此 SwiftUI 不保留它(它必须保留在 View之外)。

换句话说—— SwiftUI 似乎保留了 @StateObjectstrong引用和 @ObservedObjectunowned引用。

保留源与非保留源 预览行为来源,约8:30。

比如说:

@ObservedObject var book: BookModel

还有

@StateObject var book: BookModel

@ObservedObject不拥有实例 book,它的责任是管理实例的生命周期。

但是当你想把你的观察对象 book的生命周期和你的视图绑定在一起时,就像在 @State中一样,你可以使用 @StateObject

在这种情况下,SwiftUI 将拥有可观察对象,创建和销毁将与视图的生命周期相关联

SwiftUI 将在视图的整个生命周期中保持对象活动

这对于昂贵的资源来说是非常好的,您不需要再摆弄 onDisappear来释放资源。

此澄清摘自 WWDC2020SwiftUI 中的数据要素