Storing Enums in UserDefaults
NSUserDefaults is one of the core classes of OpenStep that have been there since its initial specification.
NSUserDefaults was designed and implemented around NSMutableDictionary. Because of this, C scalar types such as int, float, bool, and double, have to be boxed/unboxed behind the scenes. As NSDictionary can only hold objects as values.
Status Quo
Currently, if we want to store an Enum value in UserDefaults, the easiest way is to make the Enum conform to RawRepresentable and save the raw value of the member we want to store.
enum DistanceUnit: String {
case automatic
case metric
case imperial
}
class Preferences {
let userDefaults: UserDefaults = .standard
var distanceUnit: DistanceUnit {
set {
userDefaults.set(newValue.rawValue, forKey: "distance-unit")
}
get {
guard let rawValue = userDefaults.string(forKey: "distance-unit") else {
return .automatic
}
return DistanceUnit(rawValue: rawValue) ?? .automatic
}
}
}
As you can see, this can get a little bit too verbose the more Enum-backed preferences you store.
A Better Approach
Luckily, Swift has support for extensions and generics. We can leverage this to extend UserDefaults and add support for storing and retrieving values that conform to RawRepresentable
.
// UserDefaults+RawRepresentable.swift
import Foundation
extension UserDefaults {
func value<T: RawRepresentable>(_ type: T.Type, forKey key: String) -> T? {
guard let value = object(forKey: key) as? T.RawValue else {
return nil
}
return T(rawValue: value)
}
func value<T: RawRepresentable>(_ type: T.Type, forKey key: String, default: T) -> T {
return value(type, forKey: key) ?? `default`
}
func set<T: RawRepresentable>(_ value: T, forKey key: String) {
set(value.rawValue, forKey: key)
}
}
class Preferences {
let userDefaults: UserDefaults = .standard
var distanceUnit: DistanceUnit {
set {
userDefaults.set(newValue, forKey: "distance-unit")
}
get {
userDefaults.value(DistanceUnit.self, forKey: "distance-unit", default: .automatic)
}
}
}