Initializing a @propertyWrapper in the init block of the View in SwiftUI

Note: this is asked because I am not sure if the code I wrote remains working in the future from this PR: Add init for direct database property by Jasperav · Pull Request #7 · groue/GRDBQuery · GitHub

In library GRDBQuery, there is a propertyWrapper @Query. That property wrapper will update values if they are changed in the database. The Query wrapper is automatically initialized with a database provided from the Environment of SwiftUI.

If you put things in your Environment, it means it needs to have a default value, e.g.: GRDBQuery/QueryDemoApp.swift at 5e36db6148ac24f8cbfba8ed1aa27547259b2137 · groue/GRDBQuery · GitHub. This creates an empty database, just because Environment requires it, I don't want it.

Furthermore, I want to use the Query wrapper with more parameters, I am not sure how I can do that without explicitly initializing it. The values I want to use are available when initializing the View, so I was hoping I could just explicitly initializing Query with those values. This is how I currently do it:

@Query<Player> var players: [Player]
    
init(database: DatabaseQueue) {
    _players = .init(
        request: AllPlayers(),
        database: database
    )
}

This uses a private api I guess, because I need to assign players to an underscore variant. I was wondering: is there a 'better' way of initializing a property wrapper explicitly? Is this legal? I can now pass in custom values in my View through the init block and directly pass those to the Query wrapper, exactly how I want it, but it uses this weird underscore, which I don't feel comfortable using.

1 Like

It does not. This is a key part of how property wrappers work, as they were pitched to swift-evolution. It is an official, supported part of the language and is not going to go away.

Also, note that it is indeed part of the language, not an API.

3 Likes

The underscore in this case doesn’t reference private API. Instead, it’s part of the desugaring of property wrappers. This desugaring is part of the ABI, so won’t change.

I agree that assigning to the underscored, generated property isn’t the most lovely syntax. Unfortunately it’s often the only option. Your use of it here seems perfectly fine to me.

This isn’t specific to SwiftUI either, but is a general feature of property wrappers.


@propertyWrapper
struct MyWrapper {
    var wrappedValue: Int
}

struct Test {
    @MyWrapper var value: Int
    
    init(value: Int) {
        _value = .init(wrappedValue: value * 2)
    }
}

let t = Test(value: 2)
print(t)

Outputs:

Test(_value: Page_Contents.MyWrapper(wrappedValue: 4))

Note that only _value appears in the output. value itself is a computed property.

2 Likes

I just came across this subject when initializing a wrapped property in an actor's initializer. And I'm wondering where the underscore syntax is officially documented – but first my use case:

This may cause problems:

This solves it:

The official documentation shows how a property wrapper could be used without the annotation syntax, manually declaring a variable for the actual wrapper struct with underscore prefix:

You could write code that uses the behavior of a property wrapper, without taking advantage of the special attribute syntax. For example, here’s a version of SmallRectangle from the previous code listing that wraps its properties in the TwelveOrLess structure explicitly, instead of writing @TwelveOrLess as an attribute: ...

The documentation however does not hint to the fact that this actually is how the compiler translates property wrappers and that the "underscore-variable" is actually always there. I find this odd, as this is an integral part of the language and quite relevant when using SwiftUI and/or actors. Am I looking in the wrong place?

It's documented in the “Language Reference”, not in the “Language Guide”.

2 Likes

Ah perfect! :pray:t2:

I should include the reference in my annual re-read of the book :smile: