So how exactly does the ownership work with State<T> and Binding<T> wrappers?

so i was fiddling around with recreating the State<T> and Binding<T> property wrappers, and i'm really confused that they're both declared structs, presumably with “value” semantics like everything else in the language, yet the Binding<T> should be able to mutate the State<T> by reference, and i can’t seem to construct the closures to make the Binding<T> out of.

@_propertyWrapper 
struct State<Value> 
{
    var value:Value 
    
    mutating 
    func binding() -> Binding<Value> 
    {
        .init(get: { self.value }, set: { self.value = $0 })
    }
}
@_propertyWrapper 
struct Binding<Value> 
{
    private 
    let get:() -> Value, 
        set:(Value) -> ()
    var value:Value 
    {
        get 
        {
            self.get()
        }
        set(value)
        {
            self.set(value)
        }
    }

    init(get:@escaping () -> Value, set:@escaping (Value) -> ()) 
    {
        self.get = get 
        self.set = set
    }
}
binding.swift:9:20: error: escaping closure captures mutating 'self' parameter
        .init(get: { self.value }, set: { self.value = $0 })
                   ^
binding.swift:9:27: note: captured here
        .init(get: { self.value }, set: { self.value = $0 })
                          ^
binding.swift:9:41: error: escaping closure captures mutating 'self' parameter
        .init(get: { self.value }, set: { self.value = $0 })
                                        ^
binding.swift:9:54: note: captured here
        .init(get: { self.value }, set: { self.value = $0 })

does State<T> then actually wrap a class type, and pass (weak?) self to the Binding<T>’s initializer? is it a copy on write type, that just lets the Binding<T> instance bypass the copy?

2 Likes

Maybe like this?

@propertyDelegate
@dynamicMemberLookup
struct State<Value> {
    private let getValue: () -> Value
    private let setValue: (Value) -> Void
    
    var value: Value {
        get {
            return getValue()
        }
        nonmutating set {
            setValue(newValue)
        }
    }
    
    var binding: Binding<Value> {
        return Binding(getValue: { self.value }, setValue: { self.value = $0 })
    }
    
    var delegateValue: Binding<Value> {
        return binding
    }
    
    subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> Binding<Subject> {
        return Binding<Subject>(getValue: { self.value[keyPath: keyPath] }, setValue: { self.value[keyPath: keyPath] = $0 })
    }
}

@propertyDelegate
struct Binding<Value> {
    private let getValue: () -> Value
    private let setValue: (Value) -> Void
    var value: Value {
        get {
            return getValue()
        }
        nonmutating set {
            setValue(newValue)
        }
    }
    
    init(getValue: @escaping () -> Value, setValue: @escaping (Value) -> Void) {
        self.getValue = getValue
        self.setValue = setValue
    }
}

i don't think thats how it works,, you can't use it without an init(initialValue:) initializer:

struct S 
{
    @State 
    var model:Int = 0
}
51:9: error: initializing property 'model' with wrapper 'State' that lacks an 'init(initialValue:)' initializer
    var model:Int = 0
        ^           ~
50:5: note: initialize the property wrapper type directly with '(...') on the attribute
    @State 
    ^
3:8: note: generic struct 'State' declared here
struct State<Value> {

so you need

    init(initialValue:Value) 
    {
        self.value = initialValue
    }

but that doesn't work as value has no storage

What if State use UnsafeMutablePointer<Value> as storage?

private let pointer: UnsafeMutablePointer<Value>
init(initialValue value: Value) {
   pointer = UnsafeMutablePointer<Value>.allocate(capacity: 1)
   pointer.assign(repeating: value, count: 1)
}

Could it be that State relies on framework-wide shared storage, esp. since it has update function? It wouldn't too foreign of a concept as Apple has done it at many places before.

I'd be weird if State struct itself is persistent considering that it is init every time body is called (which is every time State registers change). It could be that ViewBuilder is tracking its hierarchy somehow.

It was stated a couple of times during the talks that the @State properties had their storage managed by the layout system. It would therefore make sense that some sort of pointer would be passed in to the View initializer for those properties.

Yup, they are quite good. Even without the videos, Apple quite likes the notion of I'll manage that for you, like IBOutlet, NSManagedObject, so it's not even hard to guess.