Search code examples
iosswiftproperty-wrapper

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?


Solution

  • 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.