Skip to content

Instantly share code, notes, and snippets.

@adam-zethraeus
Last active December 11, 2021 10:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adam-zethraeus/028c80d0802e60a877debf36251f99b1 to your computer and use it in GitHub Desktop.
Save adam-zethraeus/028c80d0802e60a877debf36251f99b1 to your computer and use it in GitHub Desktop.
Strongly Typed UserDefaults
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