| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- //
- // ObservableUserDefaultsValue.swift
- // LoopFollow
- //
- // Created by Jonas Björkert on 2024-07-24.
- // Copyright © 2024 Jon Fawcett. All rights reserved.
- //
- import Foundation
- import Combine
- /// An observable value holder that keeps its internal data (the value) synchronized with its UserDefaults value.
- /// The value is read from UserDefaults on initialization (if exists, otherwise a default value is used) and written when the value changes.
- class ObservableUserDefaultsValue<T: AnyConvertible & Equatable>: ObservableObject, UserDefaultsAnyValue {
- // user defaults key (UserDefaultsAnyValue protocol implementation)
- let key: String
- typealias ValueType = T
- // The value (strong typed)
- @Published var value: T {
- didSet {
- // Continue only if the new value is different from the old value
- guard self.value != oldValue else { return }
- if let validation = validation {
- guard let validatedValue = validation(value) else {
- value = oldValue
- return
- }
- value = validatedValue
- }
- // Store value to user defaults
- ObservableUserDefaultsValue.defaults.setValue(value.toAny(), forKey: key)
- // Execute custom closure
- onChange?(value)
- // Notify observers
- observers.values.forEach { $0(value) }
- // Notify UserDefaultsValueGroups that value has changed
- UserDefaultsValueGroups.valueChanged(self)
- }
- }
- /// Get/set the value from Any value (UserDefaultsAnyValue protocol implementation)
- var anyValue: Any? {
- get {
- return self.value.toAny()
- }
- set {
- guard let newValue = T.fromAny(newValue) as T? else {
- return
- }
- self.value = newValue
- }
- }
- /// Is there this key already stored in UserDefaults?
- var exists: Bool {
- return ObservableUserDefaultsValue.defaults.object(forKey: key) != nil
- }
- // On change closure
- private let onChange: ((T) -> ())?
- // Validate & transform closure : given the new value, validate it; if validation passes, return the new value;
- // if validation fails, transform the value, returning a modified version or return nil and the change will not happen
- private let validation: ((T) -> T?)?
- // Value change observers
- private var observers: [UUID : (T) -> Void] = [:]
- // User defaults used for persistence
- private class var defaults: UserDefaults {
- return UserDefaults(suiteName: AppConstants.APP_GROUP_ID)!
- }
- init(key: String, default defaultValue: T, onChange: ((T) -> Void)? = nil, validation: ((T) -> T?)? = nil) {
- self.key = key
- self.onChange = onChange
- self.validation = validation
- if let anyValue = ObservableUserDefaultsValue.defaults.object(forKey: key), let value = T.fromAny(anyValue) as T? {
- self.value = validation?(value) ?? value
- } else {
- self.value = defaultValue
- }
- }
- /// Insert this value in a group, useful for observing changes in the whole group, instead of particular values
- func group(_ groupName: String) -> Self {
- UserDefaultsValueGroups.add(self, to: groupName)
- return self
- }
- /// Register observers, will be notified when value changes
- @discardableResult
- func observeChanges(using closure: @escaping (T) -> Void) -> ObservationToken {
- let id = UUID()
- observers[id] = closure
- return ObservationToken { [weak self] in
- self?.observers.removeValue(forKey: id)
- }
- }
- func setNil(key: String) {
- ObservableUserDefaultsValue.defaults.removeObject(forKey: key)
- }
- }
|