Search code examples
swiftproperty-wrapper

How to access @propertyWrapper argument in Swift


Let's examing following code:

@propertyWrapper
struct Argument {
    var wrappedValue: Int?
    var argument: String
}

struct Model {
    @Argument var property: Int?
    @Argument(argument: "bar") var foo: Int? = nil
}

extension Model {
    init(property: Int, argument: String = "hello") {
        _property = Argument(wrappedValue: property, argument: argument)
    }
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let model = Model(property: 5)
        print(model)
    }

}

Output looks like this:

Model(_property: propertyWrapper.Argument(wrappedValue: Optional(5), argument: "hello"), _foo: propertyWrapper.Argument(wrappedValue: nil, argument: "bar"))

So the printing function somehow has access to a property wrapper argument. However model.property is just Int?, the same as model.foo. How can I extract arguments like "hello" and "bar" like this?

print(model.property.argument) // prints "hello"
print(model.foo.argument) // prints "bar"

Is this possible?


Solution

  • You can access foo and property as an Argument inside Model. Every property wrapped with a property wrapper declares a private property of that property wrapper's type, prefixed with an underscore. You are even assigning to the one for property in the initialiser.

    So under the hood, property is actually declared like:

    private var _property: Argument
    var property: Int? {
        get { _property.wrappedValue }
        set { _property.wrappedValue = newValue }
    }
    

    Since it's private, you obviously can't access outside Model, but nothing stops you from exposing it :)

    // in Model
    var publicProperty: Argument {
        get { _property }
        // not sure if a setter would be useful...
        // set { _property = newValue }
    }
    

    Then you can easily print it:

    print(model.publicProperty.argument)
    

    You can even do this by modifying the property wrapper instead, by making the property wrapper be its projected value:

    // in Argument
    var projectedValue: Argument {
        get { self }
        // not sure if a setter would be useful...
        // set { self = newValue }
    }
    
    // Usage: prefixing with a dollar sign gets the projected value
    print(model.$property.argument)