RCNConfigContent.swift 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. import Foundation
  2. import FirebaseCore // For FIRLogger
  3. // --- Placeholder Types ---
  4. typealias RCNConfigDBManager = AnyObject // Keep placeholder
  5. // Assume RemoteConfigValue, RemoteConfigSource, DBKeys, RCNUpdateOption are defined elsewhere
  6. // --- Helper Types (Assume these are defined elsewhere or inline if simple) ---
  7. // Assuming RemoteConfigValue is defined
  8. @objc(FIRRemoteConfigValue) public class RemoteConfigValue: NSObject, NSCopying {
  9. let valueData: Data
  10. let source: RemoteConfigSource
  11. init(data: Data, source: RemoteConfigSource) {
  12. self.valueData = data; self.source = source; super.init()
  13. }
  14. override convenience init() { self.init(data: Data(), source: .staticValue) }
  15. @objc public func copy(with zone: NSZone? = nil) -> Any { return self }
  16. }
  17. // Assuming RemoteConfigSource is defined
  18. @objc(FIRRemoteConfigSource) public enum RemoteConfigSource: Int {
  19. case remote = 0
  20. case defaultValue = 1
  21. case staticValue = 2
  22. }
  23. // Placeholder for RemoteConfigUpdate (assuming definition from previous tasks)
  24. @objc(FIRRemoteConfigUpdate) public class RemoteConfigUpdate: NSObject {
  25. @objc public let updatedKeys: Set<String>
  26. init(updatedKeys: Set<String>) { self.updatedKeys = updatedKeys; super.init() }
  27. }
  28. // Define RCNDBSource enum (assuming raw values)
  29. enum RCNDBSource: Int {
  30. case remote = 0 // Corresponds to Fetched
  31. case active = 1
  32. case defaultValue = 2
  33. case staticValue = 3 // Not used for DB storage?
  34. }
  35. // Define DBKeys enum (assuming keys)
  36. enum DBKeys {
  37. static let rolloutFetchedMetadata = "rolloutFetchedMetadata"
  38. static let rolloutActiveMetadata = "rolloutActiveMetadata"
  39. }
  40. // Placeholder for closure type until DB Manager is translated
  41. // Needs to match the expected signature for the `loadMain` selector
  42. typealias RCNDBLoadCompletion = @convention(block) (Bool, [String: [String: RemoteConfigValue]]?, [String: [String: RemoteConfigValue]]?, [String: [String: RemoteConfigValue]]?, [String: Any]?) -> Void
  43. typealias RCNDBCompletion = @convention(block) (Bool, [String: Any]?) -> Void // Simplified completion for other DB operations
  44. typealias RCNDBPersonalizationCompletion = @convention(block) (Bool, [String: Any]?, [String: Any]?, Any?, Any?) -> Void
  45. /// Manages the fetched, active, and default config states, including personalization and rollout metadata.
  46. /// Handles loading from and saving to the database (via RCNConfigDBManager).
  47. /// Note: Internal state requires synchronization, handled by blocking reads until initial load completes.
  48. /// Modifications are expected to happen serially via RemoteConfig's queue.
  49. class RCNConfigContent { // Not public
  50. // MARK: - Properties
  51. // TODO: Replace placeholder DBManager with actual translated class and init
  52. @objc static let shared = RCNConfigContent(dbManager: RCNConfigDBManager()) // Use DB placeholder init
  53. // Config States (protected by initial load blocking)
  54. private var _fetchedConfig: [String: [String: RemoteConfigValue]] = [:]
  55. private var _activeConfig: [String: [String: RemoteConfigValue]] = [:]
  56. private var _defaultConfig: [String: [String: RemoteConfigValue]] = [:]
  57. // Metadata (protected by initial load blocking)
  58. private var _fetchedPersonalization: [String: Any] = [:]
  59. private var _activePersonalization: [String: Any] = [:]
  60. private var _fetchedRolloutMetadata: [[String: Any]] = [] // Array of dictionaries
  61. private var _activeRolloutMetadata: [[String: Any]] = []
  62. // Dependencies & State
  63. private let dbManager: RCNConfigDBManager // Placeholder
  64. private let bundleIdentifier: String
  65. private let dispatchGroup = DispatchGroup() // Used to block reads until DB load finishes
  66. private var isConfigLoadFromDBCompleted = false // Tracks if initial load finished
  67. private var isDatabaseLoadAlreadyInitiated = false // Prevents multiple load attempts
  68. // Constants
  69. private let databaseLoadTimeoutSecs: TimeInterval = 30.0 // From ObjC kDatabaseLoadTimeoutSecs
  70. // MARK: - Initialization
  71. // Private designated initializer
  72. init(dbManager: RCNConfigDBManager) {
  73. self.dbManager = dbManager
  74. if let bundleID = Bundle.main.bundleIdentifier, !bundleID.isEmpty {
  75. self.bundleIdentifier = bundleID
  76. } else {
  77. self.bundleIdentifier = ""
  78. // TODO: Log warning - FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038", ...)
  79. }
  80. // Start loading data asynchronously
  81. loadConfigFromPersistence()
  82. }
  83. /// Kicks off the asynchronous load from the database.
  84. private func loadConfigFromPersistence() {
  85. guard !isDatabaseLoadAlreadyInitiated else { return }
  86. isDatabaseLoadAlreadyInitiated = true
  87. // Enter group for main config load
  88. dispatchGroup.enter()
  89. // Explicitly type the completion handler block to pass to perform selector
  90. let mainCompletion: RCNDBLoadCompletion = { [weak self] success, fetched, active, defaults, rollouts in
  91. guard let self = self else { return }
  92. self._fetchedConfig = fetched ?? [:]
  93. self._activeConfig = active ?? [:]
  94. self._defaultConfig = defaults ?? [:]
  95. // Extract rollout metadata
  96. self._fetchedRolloutMetadata = rollouts?[DBKeys.rolloutFetchedMetadata] as? [[String: Any]] ?? []
  97. self._activeRolloutMetadata = rollouts?[DBKeys.rolloutActiveMetadata] as? [[String: Any]] ?? []
  98. self.dispatchGroup.leave() // Leave group for main config load
  99. }
  100. // DB Interaction - Keep selector
  101. // func loadMain(bundleIdentifier: String, completionHandler: @escaping RCNDBLoadCompletion)
  102. dbManager.perform(#selector(RCNConfigDBManager.loadMain(bundleIdentifier:completionHandler:)),
  103. with: bundleIdentifier,
  104. with: mainCompletion as Any) // Pass block as Any
  105. // Enter group for personalization load
  106. dispatchGroup.enter()
  107. // Explicitly type the personalization completion handler
  108. // Adapting parameters based on ObjC impl - need verification after DB translation
  109. let personalizationCompletion: RCNDBPersonalizationCompletion = {
  110. [weak self] success, fetchedP13n, activeP13n, _, _ in // Ignore last two params
  111. guard let self = self else { return }
  112. self._fetchedPersonalization = fetchedP13n ?? [:]
  113. self._activePersonalization = activeP13n ?? [:]
  114. self.dispatchGroup.leave() // Leave group for personalization load
  115. }
  116. // DB Interaction - Placeholder Selector (Method needs translation in DB Manager)
  117. // func loadPersonalization(completionHandler: RCNDBLoadCompletion) - Assuming similar signature for now
  118. dbManager.perform(#selector(RCNConfigDBManager.loadPersonalization(completionHandler:)),
  119. with: personalizationCompletion as Any) // Pass block as Any
  120. }
  121. /// Blocks until the initial database load is complete or times out.
  122. /// - Returns: `true` if the load completed successfully within the timeout, `false` otherwise.
  123. private func checkAndWaitForInitialDatabaseLoad() -> Bool {
  124. if !isConfigLoadFromDBCompleted {
  125. let result = dispatchGroup.wait(timeout: .now() + databaseLoadTimeoutSecs)
  126. if result == .timedOut {
  127. // TODO: Log error - FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000048", ...)
  128. return false
  129. }
  130. isConfigLoadFromDBCompleted = true
  131. }
  132. return true
  133. }
  134. /// Returns true if initialization succeeded (blocking call).
  135. @objc(initializationSuccessful) // Match selector in RemoteConfig
  136. func initializationSuccessful() -> Bool {
  137. // Note: The original implementation called this on a background thread.
  138. // The blocking nature is maintained here. Consider async/await if refactoring.
  139. return checkAndWaitForInitialDatabaseLoad()
  140. }
  141. // MARK: - Computed Properties (Getters with Load Blocking)
  142. @objc(fetchedConfig) // Match selector in RemoteConfig
  143. var fetchedConfig: [String: [String: RemoteConfigValue]] {
  144. _ = checkAndWaitForInitialDatabaseLoad()
  145. return _fetchedConfig
  146. }
  147. @objc(activeConfig) // Match selector in RemoteConfig
  148. var activeConfig: [String: [String: RemoteConfigValue]] {
  149. _ = checkAndWaitForInitialDatabaseLoad()
  150. return _activeConfig
  151. }
  152. @objc(defaultConfig) // Match selector in RemoteConfig
  153. var defaultConfig: [String: [String: RemoteConfigValue]] {
  154. _ = checkAndWaitForInitialDatabaseLoad()
  155. return _defaultConfig
  156. }
  157. var activePersonalization: [String: Any] { // Internal use, no @objc needed yet
  158. _ = checkAndWaitForInitialDatabaseLoad()
  159. return _activePersonalization
  160. }
  161. @objc(activeRolloutMetadata) // Match selector in RemoteConfig
  162. var activeRolloutMetadata: [[String: Any]] {
  163. _ = checkAndWaitForInitialDatabaseLoad()
  164. return _activeRolloutMetadata
  165. }
  166. // MARK: - Update Config Content
  167. /// Update config content from fetch response in JSON format.
  168. func updateConfigContentWithResponse(_ response: [String: Any], forNamespace currentNamespace: String) {
  169. _ = checkAndWaitForInitialDatabaseLoad() // Ensure initial load done before modifying
  170. guard let state = response[RCNFetchResponseKeyState] as? String else {
  171. // TODO: Log error - FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000049", ...)
  172. return
  173. }
  174. // TODO: Log Debug - FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000059", ...)
  175. switch state {
  176. case RCNFetchResponseKeyStateNoChange:
  177. handleNoChangeState(forConfigNamespace: currentNamespace)
  178. case RCNFetchResponseKeyStateEmptyConfig:
  179. handleEmptyConfigState(forConfigNamespace: currentNamespace)
  180. case RCNFetchResponseKeyStateNoTemplate:
  181. handleNoTemplateState(forConfigNamespace: currentNamespace)
  182. case RCNFetchResponseKeyStateUpdate:
  183. handleUpdateState(forConfigNamespace: currentNamespace,
  184. withEntries: response[RCNFetchResponseKeyEntries] as? [String: String] ?? [:]) // Entries are String in response
  185. handleUpdatePersonalization(response[RCNFetchResponseKeyPersonalizationMetadata] as? [String: Any])
  186. handleUpdateRolloutFetchedMetadata(response[RCNFetchResponseKeyRolloutMetadata] as? [[String: Any]])
  187. default:
  188. // TODO: Log warning - Unknown state?
  189. break
  190. }
  191. }
  192. // MARK: - State Handling Helpers
  193. private func handleNoChangeState(forConfigNamespace currentNamespace: String) {
  194. // Ensure namespace exists in fetched config dictionary, even if empty
  195. if _fetchedConfig[currentNamespace] == nil {
  196. _fetchedConfig[currentNamespace] = [:]
  197. }
  198. // No DB changes needed
  199. }
  200. private func handleEmptyConfigState(forConfigNamespace currentNamespace: String) {
  201. // Clear fetched config for namespace
  202. _fetchedConfig[currentNamespace]?.removeAll()
  203. if _fetchedConfig[currentNamespace] == nil { // Ensure entry exists even if empty
  204. _fetchedConfig[currentNamespace] = [:]
  205. }
  206. // Clear from DB
  207. // DB Interaction - Keep selector
  208. dbManager.perform(#selector(RCNConfigDBManager.deleteRecordFromMainTable(namespace:bundleIdentifier:fromSource:)),
  209. with: currentNamespace,
  210. with: bundleIdentifier,
  211. with: RCNDBSource.remote.rawValue) // Use raw value for selector
  212. }
  213. private func handleNoTemplateState(forConfigNamespace currentNamespace: String) {
  214. // Remove namespace completely
  215. _fetchedConfig.removeValue(forKey: currentNamespace)
  216. // Clear from DB
  217. // DB Interaction - Keep selector
  218. dbManager.perform(#selector(RCNConfigDBManager.deleteRecordFromMainTable(namespace:bundleIdentifier:fromSource:)),
  219. with: currentNamespace,
  220. with: bundleIdentifier,
  221. with: RCNDBSource.remote.rawValue) // Use raw value for selector
  222. }
  223. private func handleUpdateState(forConfigNamespace currentNamespace: String, withEntries entries: [String: String]) {
  224. // TODO: Log Debug - FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058", ...)
  225. // Clear DB first
  226. // DB Interaction - Keep selector
  227. dbManager.perform(#selector(RCNConfigDBManager.deleteRecordFromMainTable(namespace:bundleIdentifier:fromSource:)),
  228. with: currentNamespace,
  229. with: bundleIdentifier,
  230. with: RCNDBSource.remote.rawValue) // Use raw value for selector
  231. // Update in-memory fetched config
  232. var namespaceConfig: [String: RemoteConfigValue] = [:]
  233. for (key, valueString) in entries {
  234. guard let valueData = valueString.data(using: .utf8) else { continue }
  235. let remoteValue = RemoteConfigValue(data: valueData, source: .remote)
  236. namespaceConfig[key] = remoteValue
  237. // Save to DB
  238. let values: [Any] = [bundleIdentifier, currentNamespace, key, valueData]
  239. // DB Interaction - Keep selector
  240. dbManager.perform(#selector(RCNConfigDBManager.insertMainTable(values:fromSource:completionHandler:)),
  241. with: values,
  242. with: RCNDBSource.remote.rawValue, // Use raw value for selector
  243. with: nil) // No completion handler needed? Check ObjC
  244. }
  245. _fetchedConfig[currentNamespace] = namespaceConfig
  246. }
  247. private func handleUpdatePersonalization(_ metadata: [String: Any]?) {
  248. guard let metadata = metadata else { return }
  249. _fetchedPersonalization = metadata
  250. // DB Interaction - Keep selector (needs correct method name)
  251. // Assume: insertOrUpdatePersonalizationConfig(_:fromSource:) -> Bool
  252. _ = dbManager.perform(#selector(RCNConfigDBManager.insertOrUpdatePersonalizationConfig(_:fromSource:)),
  253. with: metadata,
  254. with: RCNDBSource.remote.rawValue) // Use raw value for selector
  255. }
  256. private func handleUpdateRolloutFetchedMetadata(_ metadata: [[String: Any]]?) {
  257. let metadataToSave = metadata ?? [] // Use empty array if nil
  258. _fetchedRolloutMetadata = metadataToSave
  259. // DB Interaction - Keep selector (needs correct method name)
  260. // Assume: insertOrUpdateRolloutTable(key:value:completionHandler:)
  261. dbManager.perform(#selector(RCNConfigDBManager.insertOrUpdateRolloutTable(key:value:completionHandler:)),
  262. with: DBKeys.rolloutFetchedMetadata,
  263. with: metadataToSave,
  264. with: nil) // No completion handler needed
  265. }
  266. // MARK: - Copy & Activation
  267. /// Copy from a given dictionary to one of the data source (Active or Default).
  268. @objc(copyFromDictionary:toSource:forNamespace:) // Match selector in RemoteConfig
  269. func copyFromDictionary(_ fromDictionary: [String: Any]?, // Can be [String: NSObject] or [String: RemoteConfigValue]
  270. toSource DBSourceRawValue: Int,
  271. forNamespace FIRNamespace: String) {
  272. _ = checkAndWaitForInitialDatabaseLoad() // Ensure loaded before copying
  273. guard let DBSource = RCNDBSource(rawValue: DBSourceRawValue) else {
  274. print("Error: Invalid DB Source \(DBSourceRawValue)")
  275. return
  276. }
  277. guard let sourceDict = fromDictionary, !sourceDict.isEmpty else {
  278. // TODO: Log Error - FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000007", ...)
  279. return
  280. }
  281. let targetDict: inout [String: [String: RemoteConfigValue]] // Use inout for modification
  282. let targetSource: RemoteConfigSource // For RemoteConfigValue creation
  283. switch DBSource {
  284. case .defaultValue:
  285. targetDict = &_defaultConfig
  286. targetSource = .defaultValue
  287. case .active:
  288. targetDict = &_activeConfig
  289. targetSource = .remote // Active values originate from remote
  290. case .remote: // Fetched
  291. print("Warning: Copying to 'Fetched' source is not typical.")
  292. targetDict = &_fetchedConfig
  293. targetSource = .remote
  294. // TODO: Log Warning - FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000008", ...)
  295. // return // Original ObjC returned here, maybe prevent writing to fetched? Let's allow for now.
  296. case .staticValue:
  297. return // Cannot copy to static
  298. @unknown default:
  299. return
  300. }
  301. // Clear existing data for this namespace in the target
  302. // DB Interaction - Keep selector
  303. dbManager.perform(#selector(RCNConfigDBManager.deleteRecordFromMainTable(namespace:bundleIdentifier:fromSource:)),
  304. with: FIRNamespace,
  305. with: bundleIdentifier,
  306. with: DBSource.rawValue) // Use raw value for selector
  307. targetDict[FIRNamespace]?.removeAll() // Clear in-memory dict
  308. var namespaceConfig: [String: RemoteConfigValue] = [:]
  309. // Check if the top-level dictionary has the namespace key (original assumption)
  310. if let configData = sourceDict[FIRNamespace] as? [String: Any] {
  311. processConfigData(configData, into: &namespaceConfig, targetSource: targetSource, FIRNamespace: FIRNamespace, DBSource: DBSource)
  312. } else if let directConfigData = sourceDict as? [String: NSObject] { // Check if sourceDict IS the namespace dict (Defaults)
  313. processConfigData(directConfigData, into: &namespaceConfig, targetSource: targetSource, FIRNamespace: FIRNamespace, DBSource: DBSource)
  314. } else if let remoteValueDict = sourceDict as? [String: RemoteConfigValue] { // Check if sourceDict IS the namespace dict (Activation)
  315. processConfigData(remoteValueDict, into: &namespaceConfig, targetSource: targetSource, FIRNamespace: FIRNamespace, DBSource: DBSource)
  316. } else {
  317. print("Warning: Could not interpret source dictionary structure for namespace '\(FIRNamespace)' during copy.")
  318. return // Could not interpret the source dictionary
  319. }
  320. targetDict[FIRNamespace] = namespaceConfig // Update in-memory dictionary
  321. }
  322. /// Helper to process the inner config data dictionary (handles both NSObject and RemoteConfigValue)
  323. private func processConfigData<T>(_ configData: [String: T],
  324. into namespaceConfig: inout [String: RemoteConfigValue],
  325. targetSource: RemoteConfigSource,
  326. FIRNamespace: String,
  327. DBSource: RCNDBSource) {
  328. for (key, value) in configData {
  329. let valueData: Data?
  330. if let rcValue = value as? RemoteConfigValue { // Activation case
  331. valueData = rcValue.valueData // Use underlying data
  332. } else if let nsObjectValue = value as? NSObject { // Defaults case
  333. // Convert NSObject to Data (mimic ObjC logic)
  334. if let data = nsObjectValue as? Data { valueData = data }
  335. else if let str = nsObjectValue as? String { valueData = str.data(using: .utf8) }
  336. else if let num = nsObjectValue as? NSNumber { valueData = num.stringValue.data(using: .utf8) }
  337. else if let date = nsObjectValue as? Date {
  338. let formatter = ISO8601DateFormatter() // Use standard format
  339. valueData = formatter.string(from: date).data(using: .utf8)
  340. } else if let array = nsObjectValue as? NSArray { // Use NSArray/NSDictionary for JSON check
  341. valueData = try? JSONSerialization.data(withJSONObject: array)
  342. } else if let dict = nsObjectValue as? NSDictionary {
  343. valueData = try? JSONSerialization.data(withJSONObject: dict)
  344. } else {
  345. // TODO: Log warning/error for unsupported default type?
  346. valueData = nil
  347. }
  348. } else {
  349. valueData = nil // Unsupported type
  350. }
  351. guard let finalData = valueData else { continue }
  352. let newValue = RemoteConfigValue(data: finalData, source: targetSource)
  353. namespaceConfig[key] = newValue
  354. // Save to DB
  355. let values: [Any] = [bundleIdentifier, FIRNamespace, key, finalData]
  356. // DB Interaction - Keep selector
  357. dbManager.perform(#selector(RCNConfigDBManager.insertMainTable(values:fromSource:completionHandler:)),
  358. with: values,
  359. with: DBSource.rawValue, // Use raw value for selector
  360. with: nil)
  361. }
  362. }
  363. /// Sets the fetched Personalization metadata to active and saves to DB.
  364. @objc(activatePersonalization) // Match selector in RemoteConfig
  365. func activatePersonalization() {
  366. _ = checkAndWaitForInitialDatabaseLoad()
  367. _activePersonalization = _fetchedPersonalization
  368. // DB Interaction - Keep selector (needs correct method name)
  369. _ = dbManager.perform(#selector(RCNConfigDBManager.insertOrUpdatePersonalizationConfig(_:fromSource:)),
  370. with: _activePersonalization,
  371. with: RCNDBSource.active.rawValue) // Use raw value for selector
  372. }
  373. /// Sets the fetched rollout metadata to active and saves to DB.
  374. @objc(activateRolloutMetadata:) // Match selector in RemoteConfig
  375. func activateRolloutMetadata(completionHandler: @escaping (Bool) -> Void) {
  376. _ = checkAndWaitForInitialDatabaseLoad()
  377. _activeRolloutMetadata = _fetchedRolloutMetadata
  378. // DB Interaction - Keep selector (needs correct method name)
  379. dbManager.perform(#selector(RCNConfigDBManager.insertOrUpdateRolloutTable(key:value:completionHandler:)),
  380. with: DBKeys.rolloutActiveMetadata,
  381. with: _activeRolloutMetadata,
  382. with: { (success: Bool, _: [String: Any]?) in // Adapt completion signature
  383. completionHandler(success)
  384. } as RCNDBCompletion?) // Cast closure type explicitly
  385. }
  386. // MARK: - Getters with Metadata / Diffing
  387. /// Gets the active config and Personalization metadata for a namespace.
  388. func getConfigAndMetadata(forNamespace FIRNamespace: String) -> [String: Any] {
  389. _ = checkAndWaitForInitialDatabaseLoad()
  390. let activeNamespaceConfig = _activeConfig[FIRNamespace] ?? [:]
  391. // Return format matches ObjC version
  392. return [
  393. RCNFetchResponseKeyEntries: activeNamespaceConfig, // Value is [String: RemoteConfigValue]
  394. RCNFetchResponseKeyPersonalizationMetadata: _activePersonalization
  395. ]
  396. }
  397. /// Returns the updated parameters between fetched and active config for a namespace.
  398. func getConfigUpdate(forNamespace FIRNamespace: String) -> RemoteConfigUpdate {
  399. _ = checkAndWaitForInitialDatabaseLoad()
  400. var updatedKeys = Set<String>()
  401. let fetchedConfig = _fetchedConfig[FIRNamespace] ?? [:]
  402. let activeConfig = _activeConfig[FIRNamespace] ?? [:]
  403. let fetchedP13n = _fetchedPersonalization
  404. let activeP13n = _activePersonalization
  405. let fetchedRollouts = getParameterKeyToRolloutMetadata(rolloutMetadata: _fetchedRolloutMetadata)
  406. let activeRollouts = getParameterKeyToRolloutMetadata(rolloutMetadata: _activeRolloutMetadata)
  407. // Diff Config Values
  408. for (key, fetchedValue) in fetchedConfig {
  409. if let activeValue = activeConfig[key] {
  410. // Compare underlying data for equality
  411. if activeValue.valueData != fetchedValue.valueData {
  412. updatedKeys.insert(key)
  413. }
  414. } else {
  415. updatedKeys.insert(key) // Added key
  416. }
  417. }
  418. for key in activeConfig.keys {
  419. if fetchedConfig[key] == nil {
  420. updatedKeys.insert(key) // Deleted key
  421. }
  422. }
  423. // Diff Personalization (compare dictionaries)
  424. // Note: This compares based on NSObject equality, might need deeper comparison if nested objects are complex.
  425. let fetchedP13nNS = fetchedP13n as NSDictionary
  426. let activeP13nNS = activeP13n as NSDictionary
  427. for key in fetchedP13nNS.allKeys as? [String] ?? [] {
  428. if activeP13nNS[key] == nil || !activeP13nNS[key]!.isEqual(fetchedP13nNS[key]!) {
  429. updatedKeys.insert(key)
  430. }
  431. }
  432. for key in activeP13nNS.allKeys as? [String] ?? [] {
  433. if fetchedP13nNS[key] == nil {
  434. updatedKeys.insert(key)
  435. }
  436. }
  437. // Diff Rollouts (compare dictionaries derived from metadata)
  438. for (key, fetchedRolloutValue) in fetchedRollouts {
  439. if let activeRolloutValue = activeRollouts[key] {
  440. if !(activeRolloutValue as NSDictionary).isEqual(to: fetchedRolloutValue as! [AnyHashable : Any]) {
  441. updatedKeys.insert(key)
  442. }
  443. } else {
  444. updatedKeys.insert(key) // Added key
  445. }
  446. }
  447. for key in activeRollouts.keys {
  448. if fetchedRollouts[key] == nil {
  449. updatedKeys.insert(key) // Deleted key
  450. }
  451. }
  452. return RemoteConfigUpdate(updatedKeys: updatedKeys) // Use actual RemoteConfigUpdate init
  453. }
  454. /// Helper to transform rollout metadata array into a dictionary keyed by parameter key.
  455. private func getParameterKeyToRolloutMetadata(rolloutMetadata: [[String: Any]]) -> [String: [String: String]] {
  456. var result: [String: [String: String]] = [:]
  457. for metadata in rolloutMetadata {
  458. guard let rolloutId = metadata[RCNFetchResponseKeyRolloutID] as? String,
  459. let variantId = metadata[RCNFetchResponseKeyVariantID] as? String,
  460. let affectedKeys = metadata[RCNFetchResponseKeyAffectedParameterKeys] as? [String] else {
  461. continue
  462. }
  463. for key in affectedKeys {
  464. if result[key] == nil {
  465. result[key] = [:]
  466. }
  467. result[key]?[rolloutId] = variantId
  468. }
  469. }
  470. return result
  471. }
  472. // MARK: - Placeholder Selectors (for @objc calls if needed)
  473. @objc func initializationSuccessfulObjc() -> Bool { return initializationSuccessful() }
  474. // DB Manager selectors (keep for placeholder interactions)
  475. @objc func loadMain(bundleIdentifier id: String, completionHandler handler: Any?) {} // Adapt signature if needed
  476. @objc func loadPersonalization(completionHandler handler: Any?) {} // Adapt signature if needed
  477. @objc func deleteRecordFromMainTable(namespace ns: String, bundleIdentifier id: String, fromSource source: Int) {}
  478. @objc func insertMainTable(values: [Any], fromSource source: Int, completionHandler handler: Any?) {}
  479. @objc func insertOrUpdatePersonalizationConfig(_ config: [String: Any], fromSource source: Int) -> Bool { return false }
  480. @objc func insertOrUpdateRolloutTable(key: String, value list: [[String: Any]], completionHandler handler: Any?) {}
  481. } // End of RCNConfigContent class
  482. // Constants used from RCNConfigConstants.h / RCNFetchResponse.h
  483. // TODO: Move to central constants file
  484. let RCNFetchResponseKeyState = "state"
  485. let RCNFetchResponseKeyStateNoChange = "NO_CHANGE"
  486. let RCNFetchResponseKeyStateEmptyConfig = "EMPTY_CONFIG"
  487. let RCNFetchResponseKeyStateNoTemplate = "NO_TEMPLATE"
  488. let RCNFetchResponseKeyStateUpdate = "UPDATE_CONFIG"
  489. let RCNFetchResponseKeyEntries = "entries"
  490. let RCNFetchResponseKeyPersonalizationMetadata = "personalizationMetadata"
  491. let RCNFetchResponseKeyRolloutMetadata = "rolloutMetadata"
  492. // Rollout metadata keys
  493. let RCNFetchResponseKeyRolloutID = "rolloutId"
  494. let RCNFetchResponseKeyVariantID = "variantId"
  495. let RCNFetchResponseKeyAffectedParameterKeys = "affectedParameterKeys"
  496. // Placeholder for RemoteConfigUpdate if not defined elsewhere
  497. //@objc(FIRRemoteConfigUpdate) public class RemoteConfigUpdate: NSObject {
  498. // @objc public let updatedKeys: Set<String>
  499. // init(updatedKeys: Set<String>) { self.updatedKeys = updatedKeys; super.init() }
  500. //}