如果在初始化过程中使用@Observable
带有数组集合的视图模型,则在发生变异时将触发第二个应用程序生命周期。
分解:
Root:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
MainContentView()
}
}
}
视图模型
@Observable
class ContentViewModel {
var items = [String]()
init() {
print("Init")
if items.isEmpty {}
}
deinit {
print("deinit")
}
func update() {
items.append("New")
}
}
看法:
struct ContentView: View {
@State var viewModel: ContentViewModel = .init()
var body: some View {
Button("Update", action: viewModel.update)
}
}
运行此程序时,它会打印“Init”(当然是预期的),但单击“Update”会触发完整的生命周期。
它会打印另一个“Init”。在(单个)上设置断点WindowGroup
将停止在那里。这意味着(我认为)整个过程都var body: some Scene
被触发了。
- 只需删除
items.isEmpty
,问题就消失。 - 内存图显示了
ContentViewModel
两次。 - 没有 deinit。
- 仅当使用 时才会发生
@Observable
(使用 时不会发生ObservableObject/ObservedObject
)
@StateObject
和之间的主要区别在于,每次调用视图的初始化程序时,都会调用@State
的属性初始化程序。@State
因此,在
viewModel
第一次初始化之后,您最终会在ContentViewModel
每次ContentView.init
调用后续操作时创建一个临时实例。通常情况下,临时实例会立即被取消初始化,但如果您在
@Observable
类的初始化程序中执行了一些繁重的工作,它仍然会影响性能。 的文档中指出了这一点State
。有时,由于 SwiftUI 创建依赖图的算法存在一些怪癖,因此会保留一个额外的实例。根据我的经验,我最多只看到每个
@State
.在您的情况下,该行
if items.isEmpty {}
访问了观察跟踪的属性items
,并且此访问已在中注册observationRegistrar
。这会以某种方式影响 SwiftUI 的依赖关系图,并且实例不会被释放。您可以按照文档建议的方法解决此问题 - 使其成为可选并在
task
或中初始化它onAppear
。或者,您可以使用malhal提供的属性包装器。
此属性包装器将属性初始化表达式包装到中
@autoclosure
,以便它的作用类似于@StateObject
。