@StateObject 与 @ObservedObject 当外部传递但由视图拥有时

Ric*_*hiy 7 xcode ios swift swiftui combine

基于这个答案:What is the Difference Between ObservedObject and StateObject in SwiftUI

\n

苹果文档代码在这里:https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

\n

在 SwiftUI 应用程序中,实例化对象本身@StateObject时应使用属性包装器View,以便在视图更新期间不会重新创建该对象。

\n

如果该对象在其他地方实例化,@ObservedObject则应使用包装器。

\n

然而,有一条细线使它有点不清楚:如果该对象在其他地方实例化,但“注入”到该View对象,然后该视图是该对象的唯一所有者/持有者,该怎么办?应该是@StateObject还是@ObservedObject

\n

示例代码来说明这一点:

\n
import SwiftUI\nimport Combine\nimport Foundation\n\n\nstruct ViewFactory {\n    func makeView() -> some View {\n        let viewModel = ViewModel()\n        return NameView(viewModel)\n    }\n}\n\n\nfinal class ViewModel: ObservableObject {\n    @Published var name = ""\n    init() {}\n}\n\n\nstruct NameView: View {\n\n    // Should this be an `@ObservedObject` or `@StateObject`?\n    @ObservedObject var viewModel: ViewModel\n\n    init(_ viewModel: ViewModel) {\n        self.viewModel = viewModel\n    }\n\n    var body: some View {\n        Text(viewModel.name)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

基于这篇文章:https://www.hackingwithswift.com/quick-start/swiftui/whats-the-difference- Between-observedobject-state-and-environmentobject

\n

@StateObject 和 @ObservedObject 之间有一个重要的区别,即所有权 \xe2\x80\x93 哪个视图创建了该对象,以及哪个视图只是观看它。

\n

规则是这样的:无论哪个视图第一个创建对象都必须使用 @StateObject,告诉 SwiftUI 它是数据的所有者并负责保持数据的活动。所有其他视图必须使用 @ObservedObject,告诉 SwiftUI 他们想要观察对象的更改,但不直接拥有它。

\n

看来如果要View实例化ViewModel,则必须使用 来声明它@StateObject。我的代码非常相似,唯一的区别是它ViewModel是在其他地方创建的,但View在初始化后“拥有”它。

\n

rob*_*off 9

这是一个非常有趣的问题。这里发生了一些微妙的行为。

\n

首先,请注意,您不能只更改@ObservedObject@StateObjectin NameView。它不会编译:

\n
struct NameView: View {\n    @StateObject var viewModel: ViewModel\n\n    init(_ viewModel: ViewModel) {\n        self.viewModel = viewModel\n        //   ^  Cannot assign to property: \'viewModel\' is a get-only property\n    }\n    ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n

要使其编译,您必须初始化_viewModel类型的底层存储属性StateObject<ViewModel>

\n
struct NameView: View {\n    @StateObject var viewModel: ViewModel\n\n    init(_ viewModel: ViewModel) {\n        _viewModel = .init(wrappedValue: viewModel)\n    }\n    ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但那里隐藏着一些东西。StateObject.init(wrappedValue:)声明如下:

\n
public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)\n
Run Code Online (Sandbox Code Playgroud)\n

因此,作为参数给出的表达式(就在viewModel上面)被包装在闭包中,并且不会立即求值。该闭包被存储以供以后使用,这就是它的原因@escaping

\n

正如您可能从麻烦中猜到的那样,我们必须跳过才能使其编译,这是一种奇怪的使用方式StateObject。正常使用是这样的:

\n
struct NormalView: View {\n    @StateObject var viewModel = ViewModel()\n\n  var body: some View {\n        Text(viewModel.name)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

奇怪的方式来做这件事有一些缺点。要了解其缺点,我们需要查看 makeView()orNormalView()的求值上下文。假设它看起来像这样:

\n
struct ContentView: View {\n    @Binding var count: Int\n\n    var body: some View {\n        VStack {\n            Text("count: \\(count)")\n            NormalView()\n            ViewFactory().makeView()\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

count\ 的值发生变化时,SwiftUI 将再次请求ContentViewbody,这将同时评估NormalView()makeView()

\n

So在第二次求值期间body调用NormalView(),这会创建 的另一个实例NormalViewNormalView.init创建一个调用 的闭包ViewModel(),并将该闭包传递给 StateObject.init(wrappedValue:)。但StateObject.init不会立即评估此关闭。它将其存储起来以供以后使用。

\n

然后body调用makeView(),它立即调用ViewModel()。它将 new 传递给ViewModelNameView.init后者将 new 包装ViewModel在闭包中并将闭包传递给StateObject.init(wrappedValue:)。这StateObject也不会立即评估关闭,但新的ViewModel无论如何都会创建新的闭包。

\n

返回后一段时间ContentView.body,SwiftUI 想要调用NormalView.body. 但在这样做之前,它必须确保StateObjectthis 中NormalView有一个ViewModel. 它注意到这正在替换视图层次结构中相同位置的NormalView先验,因此它检索该先验使用的并将其放入新的。它不会执行给定的闭包 ,因此它不会创建新的NormalViewViewModelNormalViewStateObjectNormalViewStateObject.initViewModel

\n

甚至后来,SwiftUI 想要调用NameView.body. 但在这样做之前,它必须确保StateObjectthis 中NameView有一个ViewModel. 它注意到这正在替换视图层次结构中相同位置的NameView先验,因此它检索该先验使用的并将其放入新的。它执行给定的闭包,因此它不使用该闭包所引用的。但是NameViewViewModelNameViewStateObjectNameViewStateObject.initViewModelViewModel无论如何,它还是被创建了。

\n

因此,您使用的奇怪方式有两个缺点@StateObject

\n
    \n
  1. ViewModel每次调用时,您都会创建一个新的makeView,即使它ViewModel可能永远不会被使用。这可能会很贵,具体取决于您的ViewModel
  2. \n
  3. 您正在创建ViewModelgetterContentView.body运行时的对象。如果创建ViewModel有副作用,这可能会使 SwiftUI 感到困惑。SwiftUI 希望bodygetter 是一个纯函数。在这种NormalView情况下,SwiftUI 正在调用StateObject\ 的闭包,此时它可能会更好地准备处理副作用。
  4. \n
\n

那么,回到你原来的问题:

\n
\n

应该是@StateObject还是@ObservedObject

\n
\n

好吧,哈哈,如果没有看到一个不那么玩具的例子,这个问题很难回答。但如果你确实需要使用@StateObject,您可能应该尝试以 \xe2\x80\x98normal\xe2\x80\x99 方式初始化它。

\n