这是一个非常有趣的问题。这里发生了一些微妙的行为。
首先,请注意你不能只是改变@ObservedObject
to @StateObject
in NameView
。它不会编译:
struct NameView: View {
@StateObject var viewModel: ViewModel
init(_ viewModel: ViewModel) {
self.viewModel = viewModel
// ^ ???? Cannot assign to property: 'viewModel' is a get-only property
}
...
}
要使其编译,您必须初始化底层_viewModel
类型的存储属性StateObject<ViewModel>
:
struct NameView: View {
@StateObject var viewModel: ViewModel
init(_ viewModel: ViewModel) {
_viewModel = .init(wrappedValue: viewModel)
}
...
}
但那里隐藏着一些东西。StateObject.init(wrappedValue:)
声明如下:
public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
所以作为参数给出的表达式(只是viewModel
上面)被包裹在一个闭包中,并且是not立即评价。该闭包被存储以供以后使用,这就是为什么它是@escaping
.
正如您可能从圈子中猜到的那样,我们必须跳过才能使其编译,这是一种奇怪的使用方式StateObject
。正常使用是这样的:
struct NormalView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
Text(viewModel.name)
}
}
And 以奇怪的方式来做有一些缺点。要了解其缺点,我们需要了解其背景makeView()
or NormalView()
被评估。假设它看起来像这样:
struct ContentView: View {
@Binding var count: Int
var body: some View {
VStack {
Text("count: \(count)")
NormalView()
ViewFactory().makeView()
}
}
}
When count
的值发生变化,SwiftUI 会询问ContentView
为其body
再次,这将评估两者NormalView()
and makeView()
again.
So body
calls NormalView()
在第二次评估期间,这会创建另一个实例NormalView
. NormalView.init
创建一个闭包,调用ViewModel()
,并将闭包传递给StateObject.init(wrappedValue:)
. But StateObject.init
does not立即评估此关闭。它将其存储起来以供以后使用。
Then body
calls makeView()
, which does call ViewModel()
立即地。它通过了新ViewModel
to NameView.init
,它包装了新的ViewModel
在闭包中并将闭包传递给StateObject.init(wrappedValue:)
. This StateObject
也不会立即评估关闭,但新的ViewModel
无论如何已经创建了。
一段时间后ContentView.body
返回,SwiftUI 要调用NormalView.body
。但在此之前,必须确保StateObject
在这个NormalView
has a ViewModel
。它注意到这NormalView
正在取代之前的NormalView
在视图层次结构中的相同位置,因此它检索ViewModel
之前使用过的NormalView
并将其放入StateObject
新的NormalView
. It does not执行给定的闭包StateObject.init
, so it 不创建新的ViewModel
.
甚至后来,SwiftUI 想要调用NameView.body
。但在此之前,必须确保StateObject
在这个NameView
has a ViewModel
。它注意到这NameView
正在取代之前的NameView
在视图层次结构中的相同位置,因此它检索ViewModel
之前使用过的NameView
并将其放入StateObject
新的NameView
. It does not执行给定的闭包StateObject.init
,所以它不使用ViewModel
该闭包引用的。但是ViewModel
无论如何都被创建了。
所以你使用的奇怪方式有两个缺点@StateObject
:
- 你正在创建一个新的
ViewModel
每次你打电话makeView
,尽管如此ViewModel
可能永远不会被使用。这可能会很贵,具体取决于您的ViewModel
.
- 您正在创建
ViewModel
而ContentView.body
getter 正在运行。如果创建ViewModel
有副作用,这可能会让 SwiftUI 感到困惑。 SwiftUI 期望body
getter 是一个纯函数。在里面NormalView
情况下,SwiftUI 正在调用StateObject
在已知时间关闭,以便更好地处理副作用。
那么,回到你原来的问题:
应该是@StateObject
or @ObservedObject
?
嗯,哈哈,如果没有看到一个不那么玩具的例子,这个问题很难回答。但如果你确实需要使用@StateObject
,您可能应该尝试以“正常”方式初始化它。