Bram
Bram

Reputation: 3273

Dynamic key property wrapper

I have a simple property wrapper written to easily get and set user preferences in the UserDefaults.

@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value

    var wrappedValue: Value {
        get { UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
}

// Example usage
@UserDefault(key: "com.example.flag", defaultValue: false)
var trackingEnabled: Bool

Now this works fine for static preferences, but say I were to support multiple users and each of them would be able to store their settings.

struct Person {
    let id: UUID
    let name: String

    @UserDefault(key: "com.example.person.\(id)", defaultValue: false)
    var trackingEnabled: Bool
}

This gives me the warning Cannot use instance member 'id' within property initializer; property initializers run before 'self' is available. Is there a way to solve this so I can easily use this property wrapper with a dynamic key?

Upvotes: 1

Views: 581

Answers (1)

jnpdx
jnpdx

Reputation: 52565

In order to use the value of id in the initialization of trackingEnabled, you can use a custom initializer for your struct:

struct Person {
    let id: UUID
    let name: String

    var trackingEnabled: UserDefault<Bool>
    
    init(id: UUID, name: String) {
        self.id = id
        self.name = name
        trackingEnabled = UserDefault(key: "com.example.person.\(id)", defaultValue: false)
    }
}

Or, you can define trackingEnabled as lazy, so that it has access to the id parameter by the time it's initialized:

struct Person {
    let id: UUID
    let name: String

    lazy var trackingEnabled = UserDefault(key: "com.example.person.\(id)", defaultValue: false)
}

Neither solution uses the @propertyWrapper aspect of UserDefault, but it doesn't seem necessary here -- it still results in the same type/functionality.

Upvotes: 2

Related Questions