UserDefaults property wrapper - Issues & Solutions
Tags: swift
Published: 2019-11-12 23:07:31

Swift 5.1, Xcode 11.2

Skip the boring: [Solution for property with Optional type](#optional-user-default) | [Improved solution form proposal](#improved-user-default) | [UPDATE: Conclusions after using in practice](#update-conclusions)

Property wrapper is the new feature in Swift 5.1. There are plenty of articles covering the topic of using this feature for many purposes. One of them is wrapping property around UserDefaults, which means using UserDefaults (`UserDefults.standard` in most cases, but this is not the only possibility) instead of backing variable for the property.

I do not want to duplicate the topic when there are so many other places where this is described very well:

- [Proposal](https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#user-defaults) - where everything started
- [NSHipster - Swift Property Wrappers](https://nshipster.com/propertywrapper/)
- [SwiftLee - Property wrappers to remove boilerplate code in Swift](https://www.avanderlee.com/swift/property-wrappers/)
- and others.

However, everyone is focusing only on the simplest cases, but no one is speaking about the issues.

## What if...

Let's take some example implementation of the property wrapper:

```swift
@propertyWrapper public struct UserDefault<T> {
    public let key: String
    public let defaultValue: T
    public var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}
```

All seams to look good and it works in the most common cases like:

```swift
@UserDefault(key: "some_flag", defaultValue: false)
public var someFlag: Bool
```

~~Maybe sometimes there will be a need to set also initial value despite the fact that we have the `defaultValue`, then we have to add following two initializers~~ No, it's a terrible idea, more about it further:

```swift
public init(key: String, defaultValue: T) {
    self.key = key
    self.defaultValue = defaultValue
}

// !!!: there was no such initializer in the proposal, it's my terrible idea
public init(wrappedValue: T, key: String, defaultValue: T) {
    self.key = key
    self.defaultValue = defaultValue
    self.wrappedValue = wrappedValue
}
```

now we ~~can~~ could use it also like this:

```swift
@UserDefault(key: "some_flag", defaultValue: false)
public var someFlag: Bool ~~= true~~
```

### But what if the type of the property will be Optional value type?

This generic struct might be adopted in such case. What happens then?
To find out I'm going to use the [UserDefaultPropertyWrapper.playground](https://github.com/kodelit/UserDefaultPropertyWrapper) available on GitHub, where:

#### 1. Property wrapper is modified like this:

```swift
@propertyWrapper public struct UserDefault<T> {
    public let key: String
    public let defaultValue: T
    public var wrappedValue: T {
        get {
            let udObject = UserDefaults.standard.object(forKey: key)
            let udValue = udObject as? T
            let value = udValue ?? defaultValue
            print("get UDObject:", udObject as Any, "UDValue:", udValue as Any, "defaultValue:", defaultValue, "returned value:", value)
            return value
        }
        set {
            print("set UDValue:", newValue as Any, "for key:", key)
            UserDefaults.standard.set(newValue as Any, forKey: key)
        }
    }
}
```

#### 2. Property is defined as the following:

```swift
class Some {
    @UserDefault(key: "optional_flag", defaultValue: false)
    public var optionalFlag: Bool?
}
```

#### 3. Let's test getter and setter:

```swift
let object = Some()

object.optionalFlag
object.optionalFlag = true
object.optionalFlag
object.optionalFlag = nil
```

Console:
    
```
Property Init with key: 'optional_flag', defaultValue: 'Optional(false)'
get UDObject: nil UDValue: Optional(nil) defaultValue: Optional(false) returned value: nil
set UDValue: Optional(true) for key: optional_flag
get UDObject: Optional(1) UDValue: Optional(Optional(true)) defaultValue: Optional(false) returned value: Optional(true)
set UDValue: nil for key: optional_flag
libc++abi.dylib: terminating with uncaught exception of type NSException
```
This is quite tricky.
First get a fix on the fact that `print(...)` method used to print console logs unwraps values, so for `Optional<Bool>(nil)` or if you will `Optional<Bool>.none` it will print `nil`, and for `Optional<Bool?>(value)` will print `Optional(value)`.

As we can see in console log line 2: UDValue is not `nil` but in fact `Optional(nil)` which means that it is wrapped twice. It is even more visible in line 4 of the console log above.

It can be simply confirmed by printing:

```swift
print(Optional<Bool>(nil))                       // nil
print(Optional<Bool?>.some(Optional<Bool>(nil))) // Optional(nil)
print(Optional<Bool?>.some(Optional<Bool>(true)))// Optional(Optional(true))
```

Why is that? Because we use the conditional cast on Optional type: `as? T` where `T` is `Bool?` so we do something like this `as? Bool?` which returns `Bool??`

### So what is happening?

#### 1. The getter

When stored value is `nil` (simply not set in `UserDefaults`) we have:

```swift
let defaultValue: Bool? = Optional<Bool>.some(false)
let udObject: Bool? = Optional<Bool>.none
let udValue: Bool?? = (Optional<Bool>.none as? Bool?) // Optional<Bool?>.some(Optional<Bool>.none)
let value: Bool? = Optional<Bool?>.some(Optional<Bool>.none) ?? defaultValue // expression returns `Optional<Bool>.none` not `defaultValue`
print("get UDObject:", udObject, "UDValue:", udValue, "defaultValue:", defaultValue, "returned value:", value)
```

So what we see here?

1. getter will never return `defaultValue` if the type of the property `T` is `Optional` type
2. returned value will be `nil` which is a valid value for `Bool?` type

The same case is with concrete value
As long as this issue causes only some unexpected behavior, there is a much worse issue.

#### 2. The setter

When we try to set `nil` to the property with Optional type the setter crashes:

![error-on-setter-crush](https://github.com/kodelit/UserDefaultPropertyWrapper/raw/master/setter-crash-on-nil.png)
    
Long story short this is also caused by the Optional in the Optional, and the same same error occurs if you write:
`UserDefaults.standard.set(Optional(Optional<Bool>(nil)), forKey: "optional_flag")`
![error-on-setter-crush](https://github.com/kodelit/UserDefaultPropertyWrapper/raw/master/user-defaults-setter-crash.png)

<a name="optional-user-default"></a>
## What we can do?

### 1. Solution for the property with Optional type. [↩](#)

Simple option and maybe preferred by some people is to use separate wrapper for optional values.

```swift
@propertyWrapper
public struct OptionalUserDefault<T> {
    public let key: String
    public var wrappedValue: T? {
        get {
            return UserDefaults.standard.object(forKey: key) as? T
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}
```

The solution is not so bad because:

- distinguishes the case where the value is optional
- there is no need to define `defautlValue` because it is not needed since we expect that the value might not be there.

However, **it does not guarantee that nobody will use `@UserDefault(key:defaultValue:)` attribute to a property with Optional type**.

<a name="improved-user-default"></a>
That's why we should fix the proposal code.

### 2. Improved solution form proposal. [↩](#)

There is another solution that allows us to use one wrapper for every mentioned case or at least make it safer.

```swift
@propertyWrapper
public struct UserDefault<T> {
    public let key: String
    public let defaultValue: T
    public var wrappedValue: T {
        get {
            let udValue = UserDefaults.standard.object(forKey: key) as? T
            switch (udValue as Any) {
            case Optional<Any>.some(let value):
                return value as! T
            case Optional<Any>.none:
                return defaultValue
            default:
                return udValue ?? defaultValue
            }
        }
        set {
            switch (newValue as Any) {
            case Optional<Any>.some(let value):
                UserDefaults.standard.set(value, forKey: key)
            case Optional<Any>.none:
                UserDefaults.standard.removeObject(forKey: key)
            default:
                UserDefaults.standard.set(newValue, forKey: key)
            }
        }
    }
}
```
<a name="update-conclusions"></a>


## UPDATE: Conclusions after using in practice. [↩](#)

My experience showed that there still is something to improve here.

### 1. The **initial value** is a very **bad idea**

I mean allowing to set initial value during initialization like this:
```swift
@UserDefault(key: "some_flag", defaultValue: false)
public var someFlag: Bool = true
```
thanks to the initializer:

```swift
/// Initializer allowing to set initialValue of the property
public init(wrappedValue: T, key: String, defaultValue: T) {
    self.key = key
    self.defaultValue = defaultValue
    self.wrappedValue = wrappedValue
}
```

The initial value may cause more harm than profits. If we have a property with an initial value, it will override any value previously stored in the UserDefaults under the given key.
    
Let say we have a user defaults key `"some_key"`  
If somewher in the code you did set value for the key `UserDefaults.standard.set(true, forKey: "some_key") `
    
And you have the following class
    
```swift
class SomeSettings {
    @UserDefault(key: "some_key", defaultValue: false)
    var someFlag: Bool = false
}
```
    
Then every time the class instance is created, it will cause overriding the previously set value in the UserDefaults with `false`.
    
**That is why we should drop the idea of the initial value in case of property wrapper for storages like UserDefaults or Keychain.**  
As long as we have a default value, it is not needed anyway.

### 2. It's good to restrict allowed types to the set of types supported by Property List format.

Since we know which types are acceptable by the Property List, we can restrict our structs to be used only with the values of selected types.

To do so, we need:
    
1. declare working protocol `PlistCompatible `.
2. make compatible types conforming to the protocol.

    ```swift
    public protocol PlistCompatible {}
    
    // MARK: - UserDefaults Compatibile Types
    
    extension String: PlistCompatible {}
    extension Int: PlistCompatible {}
    extension Double: PlistCompatible {}
    extension Float: PlistCompatible {}
    extension Bool: PlistCompatible {}
    extension Date: PlistCompatible {}
    extension Data: PlistCompatible {}
    extension Array: PlistCompatible where Element: PlistCompatible {}
    extension Dictionary: PlistCompatible where Key: PlistCompatible, Value: PlistCompatible {}
    ```

3. force the generic type `T` to conform to the protocol

    ```swift
    @propertyWrapper
    public struct UserDefault<T: PlistCompatible> {
    ```

    Our Wrappers are looking like this:
    
    ```swift
    @propertyWrapper
    public struct UserDefault<T: PlistCompatible> {
        public let key: String
        public let defaultValue: T
        public var wrappedValue: T {
            get {
                return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
            }
            set {
                UserDefaults.standard.set(newValue, forKey: key)
            }
        }
    }
        
    @propertyWrapper
    public struct OptionalUserDefault<T: PlistCompatible> {
        public let key: String
        public var wrappedValue: T? {
            get {
                return UserDefaults.standard.object(forKey: key) as? T
            }
            set {
                UserDefaults.standard.set(newValue, forKey: key)
            }
        }
    }
    ```
    
### 3. It would be handy to be able to utilize the wrapper for custom types

To hadle this case we need to write separate wrappers. These will be storing value of type `T` conforming to `RawRepresentable` with restriction that its `RawValue` is `PlistCompatible`. Our wrapper requires simple modifications:

```swift
@propertyWrapper
public struct WrappedUserDefault<T: RawRepresentable> where T.RawValue: PlistCompatible {
// ...
}
```

So we will need the following two definitions:

```swift
@propertyWrapper
public struct WrappedUserDefault<T: RawRepresentable> where T.RawValue: PlistCompatible {
    public let key: String
    public let defaultValue: T
    public var wrappedValue: T {
        get {
            guard let value = UserDefaults.standard.object(forKey: key) as? T.RawValue else {
                return defaultValue
            }
            return T.init(rawValue: value) ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue.rawValue, forKey: key)
        }
    }
}

@propertyWrapper
public struct OptionalWrappedUserDefault<T: RawRepresentable> where T.RawValue: PlistCompatible {
    public let key: String
    public var wrappedValue: T? {
        get {
            guard let value = UserDefaults.standard.object(forKey: key) as? T.RawValue else {
                return nil
            }
            return T.init(rawValue: value)
        }
        set {
            UserDefaults.standard.set(newValue?.rawValue, forKey: key)
        }
    }
}
```

Now the only thing we need is to make our custom type to conform to `RawRepresentable` protocol.

### 4. What if the key depends on something?
    
I had the case when the key was depending on some value provided in `init(id: String)`. Key would look like `account[\(id)].someFlag`
    
In this case, I needed to set the property `key` during the initialization of the class and its properties. This is very interesting and possible to implement case but also not so common so since no one is reading... :) Maybe another time.

All files and code are available on the GitHub and free to use. Enjoy.
{% github kodelit/UserDefaultPropertyWrapper no-readme %}