SwiftUI 中的 ObservedObject 和 StateObject 有什么区别

sup*_*cio 56 ios swift swiftui

如果我ObservableObject在 SwiftUI 中有一个,我可以将其称为@ObservedObject

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

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        Text(viewModel.someText)
    }
}
Run Code Online (Sandbox Code Playgroud)

或者作为@StateObject

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

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

    var body: some View {
        Text(viewModel.someText)
    }
}
Run Code Online (Sandbox Code Playgroud)

但两者之间的实际区别是什么?有没有一种情况比另一种更好,或者它们是两种完全不同的东西?

paw*_*222 73

@观察对象

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

struct ContentView: View {
  @ObservedObject var viewModel = ViewModel()
}
Run Code Online (Sandbox Code Playgroud)

相反,当视图被重绘时,@State变量将保持其值。

@StateObject

A@StateObject@ObservedObjectand的组合@State-ViewModel即使在视图被丢弃和重绘后,的实例也会被保留和重用:

struct ContentView: View {
  @StateObject var viewModel = ViewModel()
}
Run Code Online (Sandbox Code Playgroud)

表现

尽管@ObservedObject如果视图被迫经常重新创建重量级对象,它会影响性能,但当@ObservedObject它不复杂时,它应该无关紧要。

何时使用@ObservedObject

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

对于在使用它的视图中初始化的任何可观察属性,您应该使用 @StateObject。如果 ObservableObject 实例是在外部创建并传递给使用它的视图,则使用 @ObservedObject 标记您的属性。

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

有用的链接:

  • 我不清楚 ObservedObject 是否有任何用例,为什么它没有被弃用?@DavidPasztor 用例表明它们可以是等效的,但是 ObservedObject 什么时候会成为首选? (2认同)
  • @AbhijitSarkar 是的,这些属性包装器应用于 ObservableObject 类,而不是视图本身。您的视图中可以同时拥有 StateObject 和 ObservedObject 属性。 (2认同)

sam*_*ize 22

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
}
Run Code Online (Sandbox Code Playgroud)


Dáv*_*tor 15

尽管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
}
Run Code Online (Sandbox Code Playgroud)


Mac*_*nik 6

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

@ObservedObject另一方面,它只是被给定观察的一个对象View,因此不会被 SwiftUI 保留(它必须保留在 之外View)。

换句话说 - 看起来 SwiftUI 保留了的strong引用@StateObject和.unowned@ObservedObject

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


Jac*_*cky 5

这是一个例子。每次单击refresh按钮时,CountViewObserved强制销毁/重新创建 StateObjectClass,因此您可以看到0,这是不期望的。

import SwiftUI
import Combine

class StateObjectClass:ObservableObject{
    let type:String
    let id:Int
    @Published var count = 0
    init(type:String){
        self.type = type
        self.id = Int.random(in: 0...1000)
        print("type:\(type) id:\(id) init")
    }
    deinit {
        print("type:\(type) id:\(id) deinit")
    }
}

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

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

struct ContentView: View {
    @State var count = 0
    var body: some View {
        VStack{
            Text("refresh CounterView count :\(count)")
            Button("refresh"){
                count += 1
            }

            CountViewState()
                .padding()

            CountViewObserved()
                .padding()

        }
    }
}

Run Code Online (Sandbox Code Playgroud)