Deferred property wrappers

I wanted to make a pitch for a deferred style of property wrapper, that will work with lazy properties only. The purpose is that you would be able to use another (non lazy) property as a dependency for the property wrapper.

Currently we can get around this by using a class for the property wrapper, then after initialisation setting the dependency on the property wrapper. For example:

@propertyWrapper
class Foo<Baz, Bar, Value> {
    let baz: Baz
    var bar: Bar?

    var wrappedValue: Value? {
        get { bar?.get(baz) }
        set { bar?.set(newValue, for: baz)
    }

    init(baz: Baz) { 
        self.baz = baz
    }

    func configure(bar: Bar) {
        self.bar = bar
    }
}

This, however means after initialisation you have to run through all @Foo properties to then configure bar.

class Item {
    let bar: Bar
    
    @Foo(baz: Baz())
    var property: Value

    init(bar: Bar) {
        self.bar = bar
        _property.configure(bar: bar)
    }
}

Proposed Solution

Add a deferredPropertyWrapper which allows the usage of self in the initialiser of the property wrapper. Make it required that the property is marked with lazy so that the user is aware that the property wrapper is deferred until after initialisation.

@deferredPropertyWrapper
struct Foo<Baz, Bar, Value>
    let baz: Baz
    let bar: Bar
    
    var wrappedValue: Value {
        get { bar.get(baz) }
        set { bar.set(baz) }
    }

Then when you want to use the property wrapper you can use another non lazy property of self

class Item {
    let bar: Bar
    
    @Foo(baz: Baz(), bar: bar)
    lazy var property: Value
}
4 Likes

Why not just call it @lazyPropertyWrapper, like lazy[H/V]Stack?

3 Likes

My initial thinking for deferred would be that it shows that the wrapper would be initialised immediately after init had completed. @lazyPropertyWrapper however would play nicer with the use of lazy var though. Unless it made more sense to create a deferred property that is simply any property that is initialised immediately after self is.

I like this because it sounds like it would help with the one case that I've tried using property wrappers for so far: a string property that wraps the string value of a text field (and similarly for checkboxes). It's convenient to use, but setup requires a bunch of boilerplate initialization after my IBOutlets have been connected. If that initialization could happen automatically it would be really nice.

I really need something like this. Seems missing in Swift. Frequently I want to access self or use some lazy loading pattern and it's frustrating why the limitation that we can't have:

@MyWrapper lazy var foo = (something that causes an extra allocation, or needs access to self) 

I'm in a situation now where I have a property wrapper to CurrentValueSubject which I want lazy loaded (the subject), but then I get "exclusive access" crashes because the wrappedValue getter is implicitly mutating (tldr; a write to the subject causes a view to reload which reads from the value after the write). I can mark the accessor as nonmutating but then I can't have the subject lazy loaded (catch 22). If I could just mark the whole PW as lazy (instead of the inner subject) the problem would be solved.

1 Like