将
@Observable
视图模型与数组集合一起使用,如果在 init 中解决,将在突变时触发第二个应用程序生命周期。
分解:
视图模型
@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 {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Button("Update", action: viewModel.update)
}
.padding()
}
}
运行此程序时,它将打印“Init”(当然是预期的),但单击“Update”会触发完整的生命周期。
它将打印另一个“Init”。在(单个)
WindowGroup
上设置断点将在那里停止。这意味着(我认为)整个var body: some Scene
正在被触发。
items.isEmpty
,问题就消失了。ContentViewModel
两次。@Observable
时发生(如果使用 ObservableObject/ObservedObject
则不会发生)@StateObject
和@State
之间的主要区别在于,每次调用视图的初始化程序时都会调用@State
的属性初始化程序。
因此,在首次初始化
viewModel
后,每次随后调用 ContentViewModel
时,最终都会创建 ContentView.init
的临时实例。
通常,临时实例会立即取消初始化,但如果您在
@Observable
类的初始化程序中执行繁重的操作,它仍然会影响性能。 State
的文档中指出了这一点。
当 SwiftUI 时,A
属性总是实例化其默认值 实例化视图。因此,要避免副作用并 初始化默认值时的性能密集型工作。为了 例如,如果视图频繁更新,则分配新的默认对象 每次视图初始化都会变得昂贵。相反,您可以 使用State
推迟对象的创建 修饰符,仅在视图首次出现时调用一次:task(priority:_:)
struct ContentView: View { @State private var library: Library? var body: some View { LibraryView(library: library) .task { library = Library() } } }
有时,由于 SwiftUI 创建依赖图的算法中的一些怪癖,会保留一个额外的实例。根据我的经验,每个
@State
我最多只看到一个额外实例。
在您的情况下,行
if items.isEmpty {}
访问观察跟踪属性 items
,并且此访问已在 observationRegistrar
中注册。这会以某种方式影响 SwiftUI 的依赖关系图,并且实例不会被释放。
您可以通过执行文档建议的操作来解决此问题 - 将其设置为可选并在
task
或 onAppear
中初始化它。
@MainActor
@propertyWrapper struct StateObservable<T>: DynamicProperty where T: Observable & AnyObject {
@StateObject var storage: Storage
class Storage: ObservableObject {
@Published var object: T // @Published allows the object to be replaced and body will be called
init(object: T) {
self.object = object
}
}
var wrappedValue: T {
get {
return storage.object
}
nonmutating set {
storage.object = newValue
}
}
// this allows $ syntax to be used in the same View where the wrapper is declared.
var projectedValue: Bindable<T> {
get {
Bindable(storage.object)
}
}
@inlinable public init(wrappedValue thunk: @autoclosure @escaping () -> T) {
_storage = StateObject(wrappedValue: Storage(object: thunk()))
}
}
此属性包装器将属性初始值设定项表达式包装到
@autoclosure
中,因此它的作用与 @StateObject
类似。