UserDefaultsValue.swift 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // LoopFollow
  2. // UserDefaultsValue.swift
  3. // Created by Jon Fawcett on 2020-06-05.
  4. import Foundation
  5. /// A type that presents a key and a mutable value (type erased key-value)
  6. protocol UserDefaultsAnyValue {
  7. var key: String { get }
  8. var anyValue: Any? { get set }
  9. }
  10. /// A 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. Also a custom validation & onChange closure can be set on initialization.
  11. ///
  12. /// Another feature of this class is that when the value changes, the change can be observed by multiple observers. There are two observation levels: the instance level, when the observers register directly to the UserDefaultsValue instance, and group level, if the UserDefaultsValue instance is embeded in a group (UserDefaultsValueGroups class manages the groups). It is very convenient to declare related values in the same group, so when one of them changes, the group observers are notified that a change occured in that group (no need to observe each particular UserDefaultsValue instance).
  13. class UserDefaultsValue<T: AnyConvertible & Equatable>: UserDefaultsAnyValue {
  14. // user defaults key (UserDefaultsAnyValue protocol implementation)
  15. let key: String
  16. typealias ValueType = T
  17. // the value (strong typed)
  18. var value: T {
  19. didSet {
  20. // continue only if the new value is different than old value
  21. guard value != oldValue else {
  22. return
  23. }
  24. if let validation = validation {
  25. guard let validatedValue = validation(value) else {
  26. value = oldValue
  27. return
  28. }
  29. value = validatedValue
  30. }
  31. // store value to user defaults
  32. UserDefaultsValue.defaults.setValue(value.toAny(), forKey: key)
  33. // execute custom closure
  34. onChange?(value)
  35. // notify observers
  36. observers.values.forEach { $0(value) }
  37. // notify UserDefaultsValueGroups that value has changed
  38. UserDefaultsValueGroups.valueChanged(self)
  39. }
  40. }
  41. /// get/set the value from Any value (UserDefaultsAnyValue protocol implementation)
  42. var anyValue: Any? {
  43. get {
  44. return value.toAny()
  45. }
  46. set {
  47. guard let newValue = T.fromAny(newValue) as T? else {
  48. return
  49. }
  50. value = newValue
  51. }
  52. }
  53. /// is there this key already stored in UserDefaults?
  54. var exists: Bool {
  55. return UserDefaultsValue.defaults.object(forKey: key) != nil
  56. }
  57. // on change closure
  58. private let onChange: ((T) -> Void)?
  59. // validate & transform closure : giving the new value, validate it; if validations passes, return the new value; if fails, transform the value, returning a modified version or ... return nil and the change will not gonna happen
  60. private let validation: ((T) -> T?)?
  61. // value change observers
  62. private var observers: [UUID: (T) -> Void] = [:]
  63. // user defaults used for persistence
  64. private class var defaults: UserDefaults {
  65. return UserDefaults(suiteName: AppConstants.APP_GROUP_ID)!
  66. }
  67. init(key: String, default defaultValue: T, onChange: ((T) -> Void)? = nil, validation: ((T) -> T?)? = nil) {
  68. self.key = key
  69. if let anyValue = UserDefaultsValue.defaults.object(forKey: key), let value = T.fromAny(anyValue) as T? {
  70. if let validation = validation {
  71. self.value = validation(value) ?? defaultValue
  72. } else {
  73. self.value = value
  74. }
  75. } else {
  76. value = defaultValue
  77. }
  78. self.onChange = onChange
  79. self.validation = validation
  80. }
  81. /// Insert this value in a group, useful for observing changes in the whole group, instead of particular values
  82. func group(_ groupName: String) -> Self {
  83. UserDefaultsValueGroups.add(self, to: groupName)
  84. return self
  85. }
  86. /// register observers, will be notified when value changes
  87. @discardableResult
  88. func observeChanges(using closure: @escaping (T) -> Void) -> ObservationToken {
  89. let id = UUID()
  90. observers[id] = closure
  91. return ObservationToken { [weak self] in
  92. self?.observers.removeValue(forKey: id)
  93. }
  94. }
  95. func setNil(key: String) {
  96. UserDefaultsValue.defaults.removeObject(forKey: key)
  97. }
  98. }