damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

DamusUserDefaults.swift (5037B)


      1 //
      2 //  DamusUserDefaults.swift
      3 //  damus
      4 //
      5 //  Created by Daniel D’Aquino on 2023-11-25.
      6 //
      7 
      8 import Foundation
      9 
     10 /// # DamusUserDefaults
     11 ///
     12 /// This struct acts like a UserDefaults object, but is also capable of automatically mirroring values to a separate store.
     13 ///
     14 /// It works by using a specific store container as the main source of truth, and by optionally mirroring values to a different container if needed.
     15 ///
     16 /// This is useful when the data of a UserDefaults object needs to be accessible from another store container,
     17 /// as it offers ways to automatically mirror information over a different container (e.g. When using app extensions)
     18 ///
     19 /// Since it mirrors items instead of migrating them, this object can be used in a backwards compatible manner.
     20 ///
     21 /// The easiest way to use this is to use `DamusUserDefaults.standard` as a drop-in replacement for `UserDefaults.standard`
     22 /// Or, you can initialize a custom object with customizable stores.
     23 struct DamusUserDefaults {
     24     
     25     // MARK: - Helper data structures
     26     
     27     enum Store: Equatable {
     28         case standard
     29         case shared
     30         case custom(UserDefaults)
     31         
     32         func get_user_defaults() -> UserDefaults? {
     33             switch self {
     34                 case .standard:
     35                     return UserDefaults.standard
     36                 case .shared:
     37                     return UserDefaults(suiteName: Constants.DAMUS_APP_GROUP_IDENTIFIER)
     38                 case .custom(let user_defaults):
     39                     return user_defaults
     40             }
     41         }
     42     }
     43     
     44     enum DamusUserDefaultsError: Error {
     45         case cannot_initialize_user_defaults
     46         case cannot_mirror_main_user_defaults
     47     }
     48     
     49     // MARK: - Stored properties
     50     
     51     private let main: UserDefaults
     52     private let mirrors: [UserDefaults]
     53     
     54     // MARK: - Initializers
     55     
     56     init?(main: Store, mirror mirrors: [Store] = []) throws {
     57         guard let main_user_defaults = main.get_user_defaults() else { throw DamusUserDefaultsError.cannot_initialize_user_defaults }
     58         let mirror_user_defaults: [UserDefaults] = try mirrors.compactMap({ mirror_store in
     59             guard let mirror_user_default = mirror_store.get_user_defaults() else {
     60                 throw DamusUserDefaultsError.cannot_initialize_user_defaults
     61             }
     62             guard mirror_store != main else {
     63                 throw DamusUserDefaultsError.cannot_mirror_main_user_defaults
     64             }
     65             return mirror_user_default
     66         })
     67         
     68         self.main = main_user_defaults
     69         self.mirrors = mirror_user_defaults
     70     }
     71     
     72     // MARK: - Functions for feature parity with UserDefaults
     73     
     74     func string(forKey defaultName: String) -> String? {
     75         let value = self.main.string(forKey: defaultName)
     76         self.mirror(value, forKey: defaultName)
     77         return value
     78     }
     79     
     80     func set(_ value: Any?, forKey defaultName: String) {
     81         self.main.set(value, forKey: defaultName)
     82         self.mirror(value, forKey: defaultName)
     83     }
     84     
     85     func removeObject(forKey defaultName: String) {
     86         self.main.removeObject(forKey: defaultName)
     87         self.mirror_object_removal(forKey: defaultName)
     88     }
     89     
     90     func object(forKey defaultName: String) -> Any? {
     91         let value = self.main.object(forKey: defaultName)
     92         self.mirror(value, forKey: defaultName)
     93         return value
     94     }
     95     
     96     // MARK: - Mirroring utilities
     97     
     98     private func mirror(_ value: Any?, forKey defaultName: String) {
     99         for mirror in self.mirrors {
    100             mirror.set(value, forKey: defaultName)
    101         }
    102     }
    103     
    104     private func mirror_object_removal(forKey defaultName: String) {
    105         for mirror in self.mirrors {
    106             mirror.removeObject(forKey: defaultName)
    107         }
    108     }
    109 }
    110 
    111 // MARK: - Default convenience objects
    112 
    113 /// # Convenience objects
    114 ///
    115 /// - `DamusUserDefaults.standard`: will detect the bundle identifier and pick an appropriate object. You should generally use this one.
    116 /// - `DamusUserDefaults.app`: stores things on its own container, and mirrors them to the shared container.
    117 /// - `DamusUserDefaults.shared`: stores things on the shared container and does no mirroring
    118 extension DamusUserDefaults {
    119     static let app: DamusUserDefaults = try! DamusUserDefaults(main: .standard, mirror: [.shared])!    // Since the underlying behavior is very static, the risk of crashing on force unwrap is low
    120     static let shared: DamusUserDefaults = try! DamusUserDefaults(main: .shared)!                      // Since the underlying behavior is very static, the risk of crashing on force unwrap is low
    121     static var standard: DamusUserDefaults {
    122         get {
    123             switch Bundle.main.bundleIdentifier {
    124                 case Constants.MAIN_APP_BUNDLE_IDENTIFIER:
    125                     return Self.app
    126                 case Constants.NOTIFICATION_EXTENSION_BUNDLE_IDENTIFIER:
    127                     return Self.shared
    128                 default:
    129                     return Self.shared
    130             }
    131         }
    132     }
    133 }