Hi,
Previously, we would conform model objects to the ObservableObject
protocol and use the @StateObject
property wrapper when storing them to an owned binding in a View.
Now, if I understand correctly, it is recommended that we use the new @Observable
macro/protocol in place of ObservableObject
and use the @State
property wrapper rather than @StateObject
. This is my understanding from documentation articles such as Migrating from the Observable Object protocol to the Observable macro.
However, the StateObject
property wrapper has an initialiser which takes an autoclosure parameter:
extension StateObject {
public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
}
This is an extremely important initialiser for state objects that are expensive to allocate. As far as I can tell, the @State
property wrapper lacks an equivalent initialiser.
What is the recommended migration strategy for objects which made use of this on StateObject
?
Thanks
try this:
@MainActor
@propertyWrapper
public struct LazyState<T: Observable>: @preconcurrency DynamicProperty {
@State private var holder: Holder
public var wrappedValue: T {
holder.wrappedValue
}
public var projectedValue: Binding<T> {
return Binding(get: { wrappedValue }, set: { _ in })
}
public func update() {
guard !holder.onAppear else { return }
holder.setup()
}
public init(wrappedValue thunk: @autoclosure @escaping () -> T) {
_holder = State(wrappedValue: Holder(wrappedValue: thunk()))
}
}
extension LazyState {
final class Holder {
private var object: T!
private let thunk: () -> T
var onAppear = false
var wrappedValue: T {
object
}
func setup() {
object = thunk()
onAppear = true
}
init(wrappedValue thunk: @autoclosure @escaping () -> T) {
self.thunk = thunk
}
}
}
// Demo
struct LinkViewUsingLazyState: View {
@LazyState var object = TestObservationObject()
var body: some View {
Text("LazyState")
Text(object.name)
@Bindable var o = object
TextField("hello", text: $o.name)
Button("change") {
object.name = "\(Int.random(in: 0 ... 1000))"
}
}
}
@Observable
class TestObservationObject {
init() {
print("init")
}
var name = "abc"
}