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,以便在视图更新期间不会重新创建该对象。
如果该对象在其他地方实例化,@ObservedObject则应使用包装器。
然而,有一条细线使它有点不清楚:如果该对象在其他地方实例化,但“注入”到该View对象,然后该视图是该对象的唯一所有者/持有者,该怎么办?应该是@StateObject还是@ObservedObject?
示例代码来说明这一点:
\nimport 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}\nRun Code Online (Sandbox Code Playgroud)\n\n@StateObject 和 @ObservedObject 之间有一个重要的区别,即所有权 \xe2\x80\x93 哪个视图创建了该对象,以及哪个视图只是观看它。
\n规则是这样的:无论哪个视图第一个创建对象都必须使用 @StateObject,告诉 SwiftUI 它是数据的所有者并负责保持数据的活动。所有其他视图必须使用 @ObservedObject,告诉 SwiftUI 他们想要观察对象的更改,但不直接拥有它。
\n看来如果要View实例化ViewModel,则必须使用 来声明它@StateObject。我的代码非常相似,唯一的区别是它ViewModel是在其他地方创建的,但View在初始化后“拥有”它。
这是一个非常有趣的问题。这里发生了一些微妙的行为。
\n首先,请注意,您不能只更改@ObservedObject为@StateObjectin NameView。它不会编译:
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}\nRun Code Online (Sandbox Code Playgroud)\n要使其编译,您必须初始化_viewModel类型的底层存储属性StateObject<ViewModel>:
struct NameView: View {\n @StateObject var viewModel: ViewModel\n\n init(_ viewModel: ViewModel) {\n _viewModel = .init(wrappedValue: viewModel)\n }\n ...\n}\nRun Code Online (Sandbox Code Playgroud)\n但那里隐藏着一些东西。StateObject.init(wrappedValue:)声明如下:
public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)\nRun Code Online (Sandbox Code Playgroud)\n因此,作为参数给出的表达式(就在viewModel上面)被包装在闭包中,并且不会立即求值。该闭包被存储以供以后使用,这就是它的原因@escaping。
正如您可能从麻烦中猜到的那样,我们必须跳过才能使其编译,这是一种奇怪的使用方式StateObject。正常使用是这样的:
struct NormalView: View {\n @StateObject var viewModel = ViewModel()\n\n var body: some View {\n Text(viewModel.name)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n以奇怪的方式来做这件事有一些缺点。要了解其缺点,我们需要查看 makeView()orNormalView()的求值上下文。假设它看起来像这样:
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}\nRun Code Online (Sandbox Code Playgroud)\n当count\ 的值发生变化时,SwiftUI 将再次请求ContentView它body,这将同时评估NormalView()和makeView()。
So在第二次求值期间body调用NormalView(),这会创建 的另一个实例NormalView。NormalView.init创建一个调用 的闭包ViewModel(),并将该闭包传递给 StateObject.init(wrappedValue:)。但StateObject.init不会立即评估此关闭。它将其存储起来以供以后使用。
然后body调用makeView(),它会立即调用ViewModel()。它将 new 传递给ViewModel,NameView.init后者将 new 包装ViewModel在闭包中并将闭包传递给StateObject.init(wrappedValue:)。这StateObject也不会立即评估关闭,但新的ViewModel无论如何都会创建新的闭包。
返回后一段时间ContentView.body,SwiftUI 想要调用NormalView.body. 但在这样做之前,它必须确保StateObjectthis 中NormalView有一个ViewModel. 它注意到这正在替换视图层次结构中相同位置的NormalView先验,因此它检索该先验使用的并将其放入新的。它不会执行给定的闭包 ,因此它不会创建新的NormalViewViewModelNormalViewStateObjectNormalViewStateObject.initViewModel。
甚至后来,SwiftUI 想要调用NameView.body. 但在这样做之前,它必须确保StateObjectthis 中NameView有一个ViewModel. 它注意到这正在替换视图层次结构中相同位置的NameView先验,因此它检索该先验使用的并将其放入新的。它不执行给定的闭包,因此它不使用该闭包所引用的。但是NameViewViewModelNameViewStateObjectNameViewStateObject.initViewModelViewModel无论如何,它还是被创建了。
因此,您使用的奇怪方式有两个缺点@StateObject:
ViewModel每次调用时,您都会创建一个新的makeView,即使它ViewModel可能永远不会被使用。这可能会很贵,具体取决于您的ViewModel。ViewModelgetterContentView.body运行时的对象。如果创建ViewModel有副作用,这可能会使 SwiftUI 感到困惑。SwiftUI 希望bodygetter 是一个纯函数。在这种NormalView情况下,SwiftUI 正在调用StateObject\ 的闭包,此时它可能会更好地准备处理副作用。那么,回到你原来的问题:
\n\n\n应该是
\n@StateObject还是@ObservedObject?
好吧,哈哈,如果没有看到一个不那么玩具的例子,这个问题很难回答。但如果你确实需要使用@StateObject,您可能应该尝试以 \xe2\x80\x98normal\xe2\x80\x99 方式初始化它。
| 归档时间: |
|
| 查看次数: |
1947 次 |
| 最近记录: |