Skip to content

Commit abe5dd4

Browse files
committed
Fix atomic config accesses
1 parent 4c1ca6d commit abe5dd4

File tree

2 files changed

+69
-39
lines changed

2 files changed

+69
-39
lines changed

FirebaseRemoteConfig/SwiftNew/ConfigContent.swift

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,59 @@ import Foundation
2121
case fetched
2222
}
2323

24+
/// The AtomicConfig class for the config variables enables atomic accesses to support multiple
25+
/// namespace usage of RemoteConfig.
26+
private class AtomicConfig {
27+
private var value: [String: [String: RemoteConfigValue]]
28+
private let lock = NSLock()
29+
30+
init(_ value: [String: [String: RemoteConfigValue]]) {
31+
self.value = value
32+
}
33+
34+
var wrappedValue: [String: [String: RemoteConfigValue]] {
35+
get { return load() }
36+
set { store(newValue: newValue) }
37+
}
38+
39+
func load() -> [String: [String: RemoteConfigValue]] {
40+
lock.lock()
41+
defer { lock.unlock() }
42+
return value
43+
}
44+
45+
func store(newValue: [String: [String: RemoteConfigValue]]) {
46+
lock.lock()
47+
defer { lock.unlock() }
48+
value = newValue
49+
}
50+
51+
func update(namespace: String, newValue: [String: RemoteConfigValue]) {
52+
lock.lock()
53+
defer { lock.unlock() }
54+
value[namespace] = newValue
55+
}
56+
57+
func update(namespace: String, key: String, rcValue: RemoteConfigValue) {
58+
lock.lock()
59+
defer { lock.unlock() }
60+
value[namespace]?[key] = rcValue
61+
}
62+
}
63+
2464
/// This class handles all the config content that is fetched from the server, cached in local
2565
/// config or persisted in database.
2666
@objc(RCNConfigContent) public
2767
class ConfigContent: NSObject {
2868
/// Active config data that is currently used.
29-
private var _activeConfig: [String: [String: RemoteConfigValue]] = [:]
69+
private var _activeConfig = AtomicConfig([:])
3070

3171
/// Pending config (aka Fetched config) data that is latest data from server that might or might
3272
/// not be applied.
33-
private var _fetchedConfig: [String: [String: RemoteConfigValue]] = [:]
73+
private var _fetchedConfig = AtomicConfig([:])
3474

3575
/// Default config provided by user.
36-
private var _defaultConfig: [String: [String: RemoteConfigValue]] = [:]
76+
private var _defaultConfig = AtomicConfig([:])
3777

3878
/// Active Personalization metadata that is currently used.
3979
private var _activePersonalization: [String: Any] = [:]
@@ -126,9 +166,9 @@ class ConfigContent: NSObject {
126166
dbManager.loadMain(withBundleIdentifier: bundleIdentifier) { [weak self] success,
127167
fetched, active, defaults, rolloutMetadata in
128168
guard let self = self else { return }
129-
self._fetchedConfig = fetched
130-
self._activeConfig = active
131-
self._defaultConfig = defaults
169+
self._fetchedConfig.store(newValue: fetched)
170+
self._activeConfig.store(newValue: active)
171+
self._defaultConfig.store(newValue: defaults)
132172
self
133173
._fetchedRolloutMetadata =
134174
rolloutMetadata[ConfigConstants.rolloutTableKeyFetchedMetadata] as? [[String: Any]] ?? []
@@ -173,14 +213,14 @@ class ConfigContent: NSObject {
173213

174214
switch dbSource {
175215
case .default:
176-
toDictionary = _defaultConfig
216+
toDictionary = defaultConfig()
177217
source = .default
178218
case .fetched:
179219
RCLog.warning("I-RCN000008",
180220
"This shouldn't happen. Destination dictionary should never be pending type.")
181221
return
182222
case .active:
183-
toDictionary = _activeConfig
223+
toDictionary = activeConfig()
184224
source = .remote
185225
toDictionary.removeValue(forKey: firebaseNamespace)
186226
}
@@ -241,9 +281,9 @@ class ConfigContent: NSObject {
241281
}
242282

243283
if dbSource == .default {
244-
_defaultConfig = toDictionary
284+
_defaultConfig.store(newValue: toDictionary)
245285
} else {
246-
_activeConfig = toDictionary
286+
_activeConfig.store(newValue: toDictionary)
247287
}
248288
}
249289

@@ -307,27 +347,23 @@ class ConfigContent: NSObject {
307347
// MARK: - State Handling
308348

309349
func handleNoChangeState(forConfigNamespace firebaseNamespace: String) {
310-
if _fetchedConfig[firebaseNamespace] == nil {
311-
_fetchedConfig[firebaseNamespace] = [:]
350+
if fetchedConfig()[firebaseNamespace] == nil {
351+
_fetchedConfig.update(namespace: firebaseNamespace, newValue: [:])
312352
}
313353
}
314354

315355
func handleEmptyConfigState(forConfigNamespace firebaseNamespace: String) {
316-
if let _ = _fetchedConfig[firebaseNamespace] {
317-
_fetchedConfig[firebaseNamespace]?.removeAll()
318-
} else {
319-
// If namespace has empty status and it doesn't exist in _fetchedConfig, we will
320-
// still add an entry for that namespace. Even if it will not be persisted in database.
321-
_fetchedConfig[firebaseNamespace] = [:]
322-
}
356+
// If namespace has empty status and it doesn't exist in _fetchedConfig, we will
357+
// still add an entry for that namespace. Even if it will not be persisted in database.
358+
_fetchedConfig.update(namespace: firebaseNamespace, newValue: [:])
323359
dbManager?.deleteRecord(fromMainTableWithNamespace: firebaseNamespace,
324360
bundleIdentifier: bundleIdentifier,
325361
fromSource: .fetched)
326362
}
327363

328364
func handleNoTemplateState(forConfigNamespace firebaseNamespace: String) {
329365
// Remove the namespace.
330-
_fetchedConfig.removeValue(forKey: firebaseNamespace)
366+
_fetchedConfig.update(namespace: firebaseNamespace, newValue: [:])
331367
dbManager?.deleteRecord(fromMainTableWithNamespace: firebaseNamespace,
332368
bundleIdentifier: bundleIdentifier,
333369
fromSource: .fetched)
@@ -341,17 +377,14 @@ class ConfigContent: NSObject {
341377
dbManager?.deleteRecord(fromMainTableWithNamespace: firebaseNamespace,
342378
bundleIdentifier: bundleIdentifier,
343379
fromSource: .fetched)
344-
if _fetchedConfig[firebaseNamespace] != nil {
345-
_fetchedConfig[firebaseNamespace]?.removeAll()
346-
} else {
347-
_fetchedConfig[firebaseNamespace] = [:]
348-
}
380+
_fetchedConfig.update(namespace: firebaseNamespace, newValue: [:])
349381

350382
// Store the fetched config values.
351383
for (key, value) in entries {
352384
guard let valueData = value.data(using: .utf8) else { continue }
353-
_fetchedConfig[firebaseNamespace]?[key] = RemoteConfigValue(data: valueData,
354-
source: .remote)
385+
_fetchedConfig
386+
.update(namespace: firebaseNamespace, key: key,
387+
rcValue: RemoteConfigValue(data: valueData, source: .remote))
355388
let values: [Any] = [bundleIdentifier, firebaseNamespace, key, valueData]
356389
updateMainTable(withValues: values, fromSource: .fetched)
357390
}
@@ -377,23 +410,23 @@ class ConfigContent: NSObject {
377410
/// If this is the first time reading the fetchedConfig, we might still be reading it from the
378411
/// database.
379412
checkAndWaitForInitialDatabaseLoad()
380-
return _fetchedConfig
413+
return _fetchedConfig.wrappedValue
381414
}
382415

383416
@objc public
384-
func activeConfig() -> [String: Any] {
417+
func activeConfig() -> [String: [String: RemoteConfigValue]] {
385418
/// If this is the first time reading the activeConfig, we might still be reading it from the
386419
/// database.
387420
checkAndWaitForInitialDatabaseLoad()
388-
return _activeConfig
421+
return _activeConfig.wrappedValue
389422
}
390423

391424
@objc public
392-
func defaultConfig() -> [String: Any] {
425+
func defaultConfig() -> [String: [String: RemoteConfigValue]] {
393426
/// If this is the first time reading the defaultConfig, we might still be reading it from the
394427
/// database.
395428
checkAndWaitForInitialDatabaseLoad()
396-
return _defaultConfig
429+
return _defaultConfig.wrappedValue
397430
}
398431

399432
@objc public
@@ -420,7 +453,7 @@ class ConfigContent: NSObject {
420453
// database.
421454
checkAndWaitForInitialDatabaseLoad()
422455
return [
423-
ConfigConstants.fetchResponseKeyEntries: _activeConfig[firebaseNamespace] as Any,
456+
ConfigConstants.fetchResponseKeyEntries: activeConfig()[firebaseNamespace] as Any,
424457
ConfigConstants.fetchResponseKeyPersonalizationMetadata: activePersonalization,
425458
]
426459
}
@@ -431,8 +464,8 @@ class ConfigContent: NSObject {
431464
// TODO: handle diff in experiment metadata.
432465
var updatedKeys = Set<String>()
433466

434-
let fetchedConfig = _fetchedConfig[firebaseNamespace] ?? [:]
435-
let activeConfig = _activeConfig[firebaseNamespace] ?? [:]
467+
let fetchedConfig = fetchedConfig()[firebaseNamespace] ?? [:]
468+
let activeConfig = activeConfig()[firebaseNamespace] ?? [:]
436469
let fetchedP13n = _fetchedPersonalization
437470
let activeP13n = _activePersonalization
438471
let fetchedRolloutMetadata = _fetchedRolloutMetadata

FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,11 @@ @interface RCNConfigSettings (Test)
118118
- (NSString *)nextRequestWithUserProperties:(NSDictionary *)userProperties;
119119
@end
120120

121-
// TODO: Restore `RCNTestRCNumTotalInstances` to end after FIRRemoteConfig is in Swift
122-
// and ConfigContent wraps fetchedConfig, etc in an actor.
123121
typedef NS_ENUM(NSInteger, RCNTestRCInstance) {
124122
RCNTestRCInstanceDefault,
125-
RCNTestRCNumTotalInstances,
126123
RCNTestRCInstanceSecondNamespace,
127124
RCNTestRCInstanceSecondApp,
128-
// RCNTestRCNumTotalInstances
125+
RCNTestRCNumTotalInstances
129126
};
130127

131128
@interface RCNRemoteConfigTest : XCTestCase {

0 commit comments

Comments
 (0)