0

I'm creating a property wrapper for UserDefaults.

What i'm trying to achieve is:

  • Setting a non-nil value to property will store it in User default.
  • Setting nil will remove the Object from UserDefault.

But below code throws compiler error:

Initializer for conditional binding must have Optional type, not 'T'

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

    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: T {
        get { UserDefaults.standard.value(forKey: key) as? T ?? defaultValue }
        set {
            if let newValue = newValue {
                UserDefaults.standard.setValue(newValue, forKey: key)
            } else {
                UserDefaults.standard.removeObject(forKey: key)
            }
        }
    }
}

// Declaration
@UserDefault("key", defaultValue: nil)
static var newUserDefaultValue: String

Is there any way to identify T is optional? as I can remove the key from UserDefaults. If not how to achieve the desired output?

Lal Krishna
  • 15,485
  • 6
  • 64
  • 84
  • Have you tried setting T as T? as the type when initalizing – Oxthor Nov 11 '19 at 15:19
  • This is tricky, there is some possibility. It's a fact that your implementation will not work good for example because when `T` is optional then expression `as? T` returns `T?` so for concrete type you will have for example `as? Bool?` which is `Bool??`. This case is discussed in greater detail in the article https://dev.to/kodelit/userdefaults-property-wrapper-issues-solutions-4lk9 – kodelit Nov 15 '19 at 11:36

1 Answers1

1

You have 2 problems in your code:

1) In your case, wrappedValue and defaultValue should be an optional:

struct UserDefault<T> {
  let key: String
  let defaultValue: T? // defaultValue should be optional

  // defaultValue should be optional
  init(_ key: String, defaultValue: T?) {
    self.key = key
    self.defaultValue = defaultValue
  }

  // wrappedValue should also be optional
  var wrappedValue: T? {
    get { UserDefaults.standard.value(forKey: key) as? T ?? defaultValue }
    set {
      if let newValue = newValue {
        UserDefaults.standard.setValue(newValue, forKey: key)
      } else {
        UserDefaults.standard.removeObject(forKey: key)
      }
    }
  }
}

2) If you initialize with nil defaultValue, you should specify type T for compiler:

// Type T should be expicitly specified. For example as String
let valueWithNilDefault = UserDefault<String>("key", defaultValue: nil)

// Type T will be determined during compile time as Int
let valueWithDefault = UserDefault("key", defaultValue: 15)
Vitalii Gozhenko
  • 9,220
  • 2
  • 48
  • 66
  • We have to use this a Property wrapper, not a variable. Also i have tried changing `wrappedValue` as `T?`. But it will make type as Always Optional. – Lal Krishna Nov 12 '19 at 06:37
  • 1
    Then you need to define custom setter function to consume optional property. But you should have defaultValue as optional, if you want to initialize it with nil – Vitalii Gozhenko Nov 12 '19 at 14:10