Wrapper for property which value should be stored in `UserDefaults.standard` under the given `key` instead of using backing variable
Property wrapper is the new feature in Swift 5.1. There are plenty of articles covering the topic about 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.
There are so many places where you can read about Property Wrappers and using it to wrap UserDefaults
:
However, everyone is focusing only on the simplest cases but no one is speaking about the issues. And there are issues but more details about them you can find in my article UserDefaults property wrapper - Issues & Solutions.
This repo contains:
UserDefaultsPropertyWrapper.swift
,Here you can take a look on the content of the UserDefaultsPropertyWrapper.swift
There is another solution which allows us to use one wrapper for every mentioned case or at least make it safer.
@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)
}
}
}
Separate wrapper for optional values
@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)
}
}
}
The solution is not so bad because:
defautlValue
because it is not needed since we expect that the value might not be there.PlistCompatible
protocol and what types confroms to it?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 {}
RawRepresentable
typesSometimes we store in UserDefaults
some representation of our custom type. To be able store and reload custom types we just need to
Make them conform to RawRepresentable
protocol
Use one of property wrappers for types represented by raw value using attributes:
@WrappedUserDefault(key:defaultValue:)
@OptionalWrappedUserDefault(key:)
@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)
}
}
}