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: https://github.com/groue/GRDBQuery/pull/7

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.

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.

1 Like

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