Last active
December 11, 2021 10:04
-
-
Save adam-zethraeus/028c80d0802e60a877debf36251f99b1 to your computer and use it in GitHub Desktop.
Strongly Typed UserDefaults
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// `CachedValue` provides a strongly typed interface to UserDefaults. | |
/// It can cache any ``Codable`` type. | |
/// | |
/// Usage: | |
/// 1. Make a struct conforming to `CachedValue` | |
/// * Add a `typealias ValueType = YourType` | |
/// * Optionally add a `static var defaultValue: YourType = YourInstance`. (Your `value` accessor will then be non-optional.) | |
/// * Optionally add a `static var cacheKey: String = "a-very-specific-string-key"`. (By default your key will be your CachedValue conforming type's name.) | |
/// * Optionally add a `static var store: UserDefaults = MyNonStandardUserDefaults()`. (By default CachedValue will use `UserDefaults.standard`.) | |
/// 2. Instantiate your type. | |
/// 3. Assign to or read from derived `.value` property. | |
/// 4. You can call `clear()` on your instance to remove the underlying data from UserDefaults. | |
/// | |
/// Example: | |
/// ``` | |
/// struct AValue: CachedValue { | |
/// typealias ValueType = String | |
/// // Note: No default value | |
/// // Derived accessor type: | |
/// // var value: String? | |
/// } | |
/// | |
/// struct BValue: CachedValue { | |
/// typealias ValueType = String | |
/// static var defaultValue: String = "Some default value" | |
/// // Derived accessor type: | |
/// // var value: String | |
/// } | |
/// | |
/// // Session 1 | |
/// var a = AValue() | |
/// var b = BValue() | |
/// print(a.value) // nil | |
/// print(b.value) // Some default value | |
/// a.value = "Value A" | |
/// b.value = "Value B" | |
/// print(a.value) // Optional("Value A") | |
/// print(b.value) // Value B | |
/// | |
/// // Session 2 | |
/// var a = AValue() | |
/// var b = BValue() | |
/// print(a.value) // Optional("Value A") | |
/// print(b.value) // Value B | |
/// a.clear() | |
/// b.clear() | |
/// print(a.value) // nil | |
/// print(b.value) // Some default value | |
/// ``` | |
public protocol CachedValue { | |
associatedtype ValueType: Codable | |
associatedtype DefaultValueType | |
associatedtype StoreType: UserDefaults | |
static var cacheKey: String { get } | |
static var defaultValue: DefaultValueType { get } | |
static var store: StoreType { get } | |
} | |
public extension CachedValue { | |
static var cacheKey: String { String(describing: Self.self) } | |
static var defaultValue: ValueType? { nil } | |
static var store: UserDefaults { UserDefaults.standard } | |
} | |
public extension CachedValue { | |
func clear() { | |
Self.store.set(nil, forKey: Self.cacheKey) | |
} | |
} | |
public extension CachedValue where DefaultValueType == ValueType { | |
var value: ValueType { | |
get { | |
let decoder = JSONDecoder() | |
if let data = Self.store.object(forKey: Self.cacheKey) as? Data, | |
let value = try? decoder.decode(ValueType.self, from: data) { | |
return value | |
} else { | |
return Self.defaultValue | |
} | |
} | |
set { | |
let encoder = JSONEncoder() | |
if let data = try? encoder.encode(newValue) { | |
Self.store.set(data, forKey: Self.cacheKey) | |
} | |
} | |
} | |
} | |
public extension CachedValue where DefaultValueType == Optional<ValueType> { | |
var value: ValueType? { | |
get { | |
let decoder = JSONDecoder() | |
if let data = Self.store.object(forKey: Self.cacheKey) as? Data, | |
let value = try? decoder.decode(ValueType.self, from: data) { | |
return value | |
} else { | |
return nil | |
} | |
} | |
set { | |
let encoder = JSONEncoder() | |
if let data = try? encoder.encode(newValue) { | |
Self.store.set(data, forKey: Self.cacheKey) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment