UserDefaultsManager.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Copyright 2024 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. import FirebaseCore
  16. /// A class that manages user defaults for Firebase Remote Config.
  17. @objc(RCNUserDefaultsManager)
  18. public class UserDefaultsManager: NSObject {
  19. /// The user defaults instance for this bundleID. NSUserDefaults is guaranteed to be thread-safe.
  20. private let userDefaults: UserDefaults
  21. /// The suite name for this user defaults instance. It is a combination of a prefix and the
  22. /// bundleID. This is because you cannot use just the bundleID of the current app as the suite
  23. /// name when initializing user defaults.
  24. private let userDefaultsSuiteName: String = ""
  25. /// The FIRApp that this instance is scoped within.
  26. private let firebaseAppName: String
  27. /// The Firebase Namespace that this instance is scoped within.
  28. private let firebaseNamespace: String
  29. /// The bundleID of the app. In case of an extension, this will be the bundleID of the parent app.
  30. private let bundleIdentifier: String
  31. static let kRCNGroupPrefix = "group"
  32. static let kRCNGroupSuffix = "firebase"
  33. let kRCNUserDefaultsKeyNamelastETag = "lastETag"
  34. let kRCNUserDefaultsKeyNamelastETagUpdateTime = "lastETagUpdateTime"
  35. let kRCNUserDefaultsKeyNameLastSuccessfulFetchTime = "lastSuccessfulFetchTime"
  36. let kRCNUserDefaultsKeyNamelastFetchStatus = "lastFetchStatus"
  37. let kRCNUserDefaultsKeyNameIsClientThrottled = "isClientThrottledWithExponentialBackoff"
  38. let kRCNUserDefaultsKeyNameThrottleEndTime = "throttleEndTime"
  39. let kRCNUserDefaultsKeyNameCurrentThrottlingRetryInterval = "currentThrottlingRetryInterval"
  40. let kRCNUserDefaultsKeyNameRealtimeThrottleEndTime = "throttleRealtimeEndTime"
  41. let kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval =
  42. "currentRealtimeThrottlingRetryInterval"
  43. let kRCNUserDefaultsKeyNameRealtimeRetryCount = "realtimeRetryCount"
  44. let kRCNUserDefaultsKeyCustomSignals = "customSignals"
  45. // Delete when ObjC tests are gone.
  46. @objc public convenience init(appName: String, bundleID: String, namespace: String) {
  47. self.init(appName: appName, bundleID: bundleID, namespace: namespace, userDefaults: nil)
  48. }
  49. @objc public init(appName: String, bundleID: String, namespace: String,
  50. userDefaults: UserDefaults? = nil) {
  51. firebaseAppName = appName
  52. bundleIdentifier = bundleID
  53. firebaseNamespace = UserDefaultsManager.validateNamespace(namespace: namespace)
  54. if let userDefaults {
  55. self.userDefaults = userDefaults
  56. } else {
  57. // Initialize the user defaults with a prefix and the bundleID. For app extensions, this will
  58. // be
  59. // the bundleID of the app extension.
  60. self.userDefaults =
  61. UserDefaultsManager.sharedUserDefaultsForBundleIdentifier(bundleIdentifier)
  62. }
  63. }
  64. private static func validateNamespace(namespace: String) -> String {
  65. if namespace.contains(":") {
  66. let components = namespace.components(separatedBy: ":")
  67. return components[0]
  68. } else {
  69. RCLog.error("I-RCN00064", "Error: Namespace \(namespace) " +
  70. "is not fully qualified app:namespace.")
  71. return namespace
  72. }
  73. }
  74. private static let sharedInstanceMapLock = NSLock()
  75. private static var sharedInstanceMap: [String: UserDefaults] = [:]
  76. /// Returns the shared user defaults instance for the given bundle identifier.
  77. ///
  78. /// - Parameter bundleIdentifier: The bundle identifier of the app.
  79. /// - Returns: The shared user defaults instance.
  80. @objc(sharedUserDefaultsForBundleIdentifier:)
  81. static func sharedUserDefaultsForBundleIdentifier(_ bundleIdentifier: String) -> UserDefaults {
  82. sharedInstanceMapLock.withLock {
  83. if let instance = sharedInstanceMap[bundleIdentifier] {
  84. return instance
  85. }
  86. let userDefaults = UserDefaults(suiteName: userDefaultsSuiteName(for: bundleIdentifier))!
  87. sharedInstanceMap[bundleIdentifier] = userDefaults
  88. return userDefaults
  89. }
  90. }
  91. /// Returns the user defaults suite name for the given bundle identifier.
  92. ///
  93. /// - Parameter bundleIdentifier: The bundle identifier of the app.
  94. /// - Returns: The user defaults suite name.
  95. @objc(userDefaultsSuiteNameForBundleIdentifier:)
  96. public static func userDefaultsSuiteName(for bundleIdentifier: String) -> String {
  97. return "\(kRCNGroupPrefix).\(bundleIdentifier).\(kRCNGroupSuffix)"
  98. }
  99. @objc public var customSignals: [String: String] {
  100. get { instanceUserDefaults[kRCNUserDefaultsKeyCustomSignals] as? [String: String] ?? [:] }
  101. set {
  102. setInstanceUserDefaultsValue(newValue, forKey: kRCNUserDefaultsKeyCustomSignals)
  103. }
  104. }
  105. /// The last ETag received from the server.
  106. @objc public var lastETag: String? {
  107. get { instanceUserDefaults[kRCNUserDefaultsKeyNamelastETag] as? String }
  108. set {
  109. if let lastETag = newValue {
  110. setInstanceUserDefaultsValue(lastETag, forKey: kRCNUserDefaultsKeyNamelastETag)
  111. }
  112. }
  113. }
  114. /// The last fetched template version.
  115. @objc public var lastFetchedTemplateVersion: String {
  116. get { instanceUserDefaults[ConfigConstants.fetchResponseKeyTemplateVersion] as? String ?? "0" }
  117. set {
  118. setInstanceUserDefaultsValue(
  119. newValue,
  120. forKey: ConfigConstants.fetchResponseKeyTemplateVersion
  121. )
  122. }
  123. }
  124. /// The last active template version.
  125. @objc public var lastActiveTemplateVersion: String {
  126. get { instanceUserDefaults[ConfigConstants.activeKeyTemplateVersion] as? String ?? "0" }
  127. set {
  128. setInstanceUserDefaultsValue(newValue, forKey: ConfigConstants.activeKeyTemplateVersion)
  129. }
  130. }
  131. /// The last ETag update time.
  132. @objc public var lastETagUpdateTime: TimeInterval {
  133. get { instanceUserDefaults[kRCNUserDefaultsKeyNamelastETagUpdateTime] as? TimeInterval ?? 0 }
  134. set {
  135. setInstanceUserDefaultsValue(newValue, forKey: kRCNUserDefaultsKeyNamelastETagUpdateTime)
  136. }
  137. }
  138. /// The last fetch time.
  139. @objc public var lastFetchTime: TimeInterval {
  140. get {
  141. instanceUserDefaults[kRCNUserDefaultsKeyNameLastSuccessfulFetchTime] as? TimeInterval ?? 0
  142. }
  143. set {
  144. setInstanceUserDefaultsValue(newValue, forKey: kRCNUserDefaultsKeyNameLastSuccessfulFetchTime)
  145. }
  146. }
  147. /// The last fetch status.
  148. @objc public var lastFetchStatus: String? {
  149. get { instanceUserDefaults[kRCNUserDefaultsKeyNamelastFetchStatus] as? String }
  150. set {
  151. if let lastFetchStatus = newValue {
  152. setInstanceUserDefaultsValue(
  153. lastFetchStatus,
  154. forKey: kRCNUserDefaultsKeyNamelastFetchStatus
  155. )
  156. }
  157. }
  158. }
  159. /// Whether the client is throttled with exponential backoff.
  160. @objc public var isClientThrottledWithExponentialBackoff: Bool {
  161. get { instanceUserDefaults[kRCNUserDefaultsKeyNameIsClientThrottled] as? Bool ?? false }
  162. set {
  163. setInstanceUserDefaultsValue(
  164. newValue,
  165. forKey: kRCNUserDefaultsKeyNameIsClientThrottled
  166. )
  167. }
  168. }
  169. /// The throttle end time.
  170. @objc public var throttleEndTime: TimeInterval {
  171. get { instanceUserDefaults[kRCNUserDefaultsKeyNameThrottleEndTime] as? TimeInterval ?? 0 }
  172. set {
  173. setInstanceUserDefaultsValue(newValue, forKey: kRCNUserDefaultsKeyNameThrottleEndTime)
  174. }
  175. }
  176. /// The current throttling retry interval in seconds.
  177. @objc public var currentThrottlingRetryIntervalSeconds: TimeInterval {
  178. get {
  179. instanceUserDefaults[
  180. kRCNUserDefaultsKeyNameCurrentThrottlingRetryInterval
  181. ] as? TimeInterval ?? 0
  182. }
  183. set {
  184. setInstanceUserDefaultsValue(
  185. newValue, forKey: kRCNUserDefaultsKeyNameCurrentThrottlingRetryInterval
  186. )
  187. }
  188. }
  189. /// The realtime retry count.
  190. @objc public var realtimeRetryCount: Int {
  191. get { instanceUserDefaults[kRCNUserDefaultsKeyNameRealtimeRetryCount] as? Int ?? 0 }
  192. set { setInstanceUserDefaultsValue(newValue, forKey: kRCNUserDefaultsKeyNameRealtimeRetryCount)
  193. }
  194. }
  195. /// The realtime throttle end time.
  196. @objc public var realtimeThrottleEndTime: TimeInterval {
  197. get {
  198. instanceUserDefaults[kRCNUserDefaultsKeyNameRealtimeThrottleEndTime] as? TimeInterval ?? 0
  199. }
  200. set {
  201. setInstanceUserDefaultsValue(
  202. newValue, forKey: kRCNUserDefaultsKeyNameRealtimeThrottleEndTime
  203. )
  204. }
  205. }
  206. /// The current realtime throttling retry interval in seconds.
  207. @objc public var currentRealtimeThrottlingRetryIntervalSeconds: TimeInterval {
  208. get {
  209. instanceUserDefaults[
  210. kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval
  211. ] as? TimeInterval ?? 0
  212. }
  213. set {
  214. setInstanceUserDefaultsValue(
  215. newValue, forKey: kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval
  216. )
  217. }
  218. }
  219. /// Resets the user defaults.
  220. @objc public func resetUserDefaults() {
  221. resetInstanceUserDefaults()
  222. }
  223. // There is a nested hierarchy for the userdefaults as follows:
  224. // [FIRAppName][FIRNamespaceName][Key]
  225. private var appUserDefaults: [String: Any] {
  226. let appPath = firebaseAppName
  227. return userDefaults.dictionary(forKey: appPath) ?? [:]
  228. }
  229. // Search for the user defaults for this (app, namespace) instance using the valueForKeyPath
  230. // method.
  231. private var instanceUserDefaults: [String: AnyHashable] {
  232. let namespacedDictionary = userDefaults.dictionary(forKey: firebaseAppName)
  233. return namespacedDictionary?[firebaseNamespace] as? [String: AnyHashable] ?? [:]
  234. }
  235. // Update users defaults for just this (app, namespace) instance.
  236. private func setInstanceUserDefaultsValue(_ value: AnyHashable, forKey key: String) {
  237. objc_sync_enter(userDefaults)
  238. defer { objc_sync_exit(userDefaults) }
  239. var appUserDefaults = appUserDefaults
  240. var appNamespaceUserDefaults = instanceUserDefaults
  241. appNamespaceUserDefaults[key] = value
  242. appUserDefaults[firebaseNamespace] = appNamespaceUserDefaults
  243. userDefaults.set(appUserDefaults, forKey: firebaseAppName)
  244. // We need to synchronize to have this value updated for the extension.
  245. userDefaults.synchronize()
  246. }
  247. // Delete any existing userdefaults for this instance.
  248. private func resetInstanceUserDefaults() {
  249. objc_sync_enter(userDefaults)
  250. defer { objc_sync_exit(userDefaults) }
  251. var appUserDefaults = appUserDefaults
  252. var appNamespaceUserDefaults = instanceUserDefaults
  253. appNamespaceUserDefaults.removeAll()
  254. appUserDefaults[firebaseNamespace] = appNamespaceUserDefaults
  255. userDefaults.set(appUserDefaults, forKey: firebaseAppName)
  256. // We need to synchronize to have this value updated for the extension.
  257. userDefaults.synchronize()
  258. }
  259. }