我正在尝试优化我的SwiftUI应用程序。我有一个奇怪的行为与ViewModel存储为@StateObject
在其视图。为了理解这个问题,我做了一个小项目来重现它。
ContentView
包含打开页中的ChildView
的按钮。ChildView
被存储为属性,因为我不想每次由用户打开工作表时都重新创建它(这有效):
struct ContentView: View {
@State private var displayingChildView = false
private let childView = ChildView()
var body: some View {
Button(action: {
displayingChildView.toggle()
}, label: {
Text("Display child view")
})
.sheet(isPresented: $displayingChildView, content: {
childView // instead of: ChildView()
})
}
}
ChildView
code:
struct ChildView: View {
@StateObject private var viewModel = ViewModel()
init() {
print("init() of ChildView")
}
var body: some View {
VStack {
Button(action: {
viewModel.add()
}, label: {
Text("Add 1 to count")
})
Text("Count: (viewModel.count)")
}
}
}
及其ViewModel
:
class ViewModel: ObservableObject {
@Published private(set) var count = 0
init() {
print("init() of ViewModel")
}
func add() {
count += 1
}
}
问题是:
ViewModel的init
在每次用户打开工作表时被调用。为什么?
由于ViewModel
是ChildView
中的@StateObject
,ChildView
只初始化一次,我期望ViewModel
也只初始化一次。
我读过一篇文章,上面说:
用@StateObject属性包装器标记的观察对象在包含它们的视图结构重绘时不会被销毁和重新实例化。
或在这里:
对于你使用的每个可观察对象使用一次@StateObject,无论你代码的哪一部分负责创建它。
所以我理解ViewModel
应该保持活着,特别是ChildView
没有被破坏。
最让我困惑的是,如果我用@ObservedObject
代替@StateObject
,它会像预期的那样工作。但是不建议在View中存储@ObservedObject
。
谁能解释为什么这个行为和如何修复它的预期(ViewModel
init应该被调用一次)?
一个可能的解决方案:
我已经找到了一个可能的解决方案来修复这个行为:
。将ViewModel
的声明移动到ContentView
:
@StateObject private var viewModel = ViewModel()
b。修改ChildView
中ViewModel
的声明为EnvironmentObject
:
@EnvironmentObject private var viewModel: ViewModel
c。并注入childView
:
childView
.environmentObject(viewModel)
这意味着它的ContentView
负责保持ChildView的ViewModel存活。它可以工作,但我发现这个解决方案相当难看:
ChildView
的所有子视图都可以通过环境对象访问ViewModel
。但这是没有意义的,因为它应该只对它的视图有用。- 我更喜欢在它的视图中声明ViewModel,而不是在它的父视图中。
这个解决方案仍然不能解释上述关于@StateObject
应该保持存活的问题…
当视图被插入到视图层次中时,SwiftUI初始化@State变量。这就是为什么您试图通过将子视图赋值给var来保持其状态失败的原因。每次显示工作表时,子视图被添加到视图层次结构中,并初始化其状态变量。
正确的方法是将viewModel传递给子视图。
struct ContentView: View {
@StateObject private var viewModel = ViewModel()
@State private var displayingChildView = false
var body: some View {
Button(action: {
displayingChildView.toggle()
}, label: {
Text("Display child view")
})
.sheet(isPresented: $displayingChildView, content: {
ChildView(viewModel: viewModel)
})
}
}
struct ChildView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Button(action: {
viewModel.add()
}, label: {
Text("Add 1 to count")
})
Text("Count: (viewModel.count)")
}
}
}
对象减慢了SwiftUI的速度,为了有效地使用它,我们需要忘记视图模型对象,学习值类型、结构体、变异函数、闭包捕获等。以下是应该做的:
struct Counter {
private(set) var count = 0
init() {
print("init() of Counter")
}
mutating func add() {
count += 1
}
}
struct ChildView: View {
@State private var counter = Counter()
init() {
print("init() of ChildView")
}
var body: some View {
VStack {
Button(action: {
counter.add()
}, label: {
Text("Add 1 to count")
})
Text("Count: (counter.count)")
}
}
}