UserDefaultsValue.swift 4.5 KB

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