Skip to content
Merged
1 change: 1 addition & 0 deletions FirebaseRemoteConfig.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ app update.
# 'FirebaseRemoteConfig/Tests/Unit/RCNConfigTest.m',
'FirebaseRemoteConfig/Tests/Unit/RCNConfigExperimentTest.m',
'FirebaseRemoteConfig/Tests/Unit/RCNConfigValueTest.m',
'FirebaseRemoteConfig/Tests/Unit/RCNPersonalizationTest.m',
# 'FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfig+FIRAppTest.m',
'FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m',
# 'FirebaseRemoteConfig/Tests/Unit/RCNThrottlingTests.m',
Expand Down
3 changes: 3 additions & 0 deletions FirebaseRemoteConfig/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# v7.1.0
- [changed] Add support for other Firebase products to integrate with Remote Config. (#6692)

# v7.0.0
- [changed] Updated `lastFetchTime` field to readonly. (#6567)
- [changed] Functionally neutral change to stop using a deprecated method in the AB Testing API. (#6543)
Expand Down
35 changes: 35 additions & 0 deletions FirebaseRemoteConfig/Sources/FIRRemoteConfig.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
#import "FirebaseRemoteConfig/Sources/RCNPersonalization.h"

/// Remote Config Error Domain.
/// TODO: Rename according to obj-c style for constants.
Expand All @@ -39,6 +40,9 @@
/// Timeout value for waiting on a fetch response.
static NSString *const kRemoteConfigFetchTimeoutKey = @"_rcn_fetch_timeout";

/// Listener for the get methods.
typedef void (^FIRRemoteConfigListener)(NSString *_Nonnull, NSDictionary *_Nonnull);

@implementation FIRRemoteConfigSettings

- (instancetype)init {
Expand All @@ -61,6 +65,7 @@ @implementation FIRRemoteConfig {
RCNConfigExperiment *_configExperiment;
dispatch_queue_t _queue;
NSString *_appName;
NSMutableArray *_listeners;
}

static NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, FIRRemoteConfig *> *>
Expand Down Expand Up @@ -154,6 +159,15 @@ - (instancetype)initWithAppName:(NSString *)appName
options:options];

[_settings loadConfigFromMetadataTable];

if (analytics) {
_listeners = [[NSMutableArray alloc] init];
RCNPersonalization *personalization =
[[RCNPersonalization alloc] initWithAnalytics:analytics];
[self addListener:^(NSString *key, NSDictionary *config) {
[personalization logArmActive:key config:config];
}];
}
}
return self;
}
Expand Down Expand Up @@ -185,6 +199,24 @@ - (void)ensureInitializedWithCompletionHandler:
});
}

/// Adds a listener that will be called whenever one of the get methods is called.
/// @param listener Function that takes in the parameter key and the config.
- (void)addListener:(nonnull FIRRemoteConfigListener)listener {
@synchronized(_listeners) {
[_listeners addObject:listener];
}
}

- (void)callListeners:(NSString *)key config:(NSDictionary *)config {
@synchronized(_listeners) {
for (FIRRemoteConfigListener listener in _listeners) {
dispatch_async(_queue, ^{
listener(key, config);
});
}
}
}

#pragma mark - fetch

- (void)fetchWithCompletionHandler:(FIRRemoteConfigFetchCompletion)completionHandler {
Expand Down Expand Up @@ -279,6 +311,7 @@ - (void)activateWithCompletion:(FIRRemoteConfigActivateChangeCompletion)completi
forNamespace:self->_FIRNamespace];
strongSelf->_settings.lastApplyTimeInterval = [[NSDate date] timeIntervalSince1970];
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated.");
[strongSelf->_configContent activatePersonalization];
[strongSelf->_configExperiment updateExperimentsWithHandler:^(NSError *_Nullable error) {
if (completion) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
Expand Down Expand Up @@ -321,6 +354,8 @@ - (FIRRemoteConfigValue *)configValueForKey:(NSString *)key {
@"Key %@ should come from source:%zd instead coming from source: %zd.", key,
(long)FIRRemoteConfigSourceRemote, (long)value.source);
}
[self callListeners:key
config:[self->_configContent getConfigAndMetadataForNamespace:FQNamespace]];
return;
}
value = self->_configContent.defaultConfig[FQNamespace][key];
Expand Down
2 changes: 2 additions & 0 deletions FirebaseRemoteConfig/Sources/RCNConfigConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ static const char *RCNRemoteConfigQueueLabel = "com.google.GoogleConfigService.F
static NSString *const RCNFetchResponseKeyEntries = @"entries";
/// Key that includes data for experiment descriptions in ABT.
static NSString *const RCNFetchResponseKeyExperimentDescriptions = @"experimentDescriptions";
/// Key that includes data for Personalization metadata.
static NSString *const RCNFetchResponseKeyPersonalizationMetadata = @"personalizationMetadata";
/// Error key.
static NSString *const RCNFetchResponseKeyError = @"error";
/// Error code.
Expand Down
6 changes: 6 additions & 0 deletions FirebaseRemoteConfig/Sources/RCNConfigContent.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,10 @@ typedef NS_ENUM(NSInteger, RCNDBSource) {
toSource:(RCNDBSource)source
forNamespace:(NSString *)FIRNamespace;

/// Sets the fetched Personalization metadata to active.
- (void)activatePersonalization;

/// Gets the active config and Personalization metadata.
- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace;

@end
94 changes: 68 additions & 26 deletions FirebaseRemoteConfig/Sources/RCNConfigContent.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ @implementation RCNConfigContent {
NSMutableDictionary *_fetchedConfig;
/// Default config provided by user.
NSMutableDictionary *_defaultConfig;
/// Active Personalization metadata that is currently used.
NSDictionary *_activePersonalization;
/// Pending Personalization metadata that is latest data from server that might or might not be
/// applied.
NSDictionary *_fetchedPersonalization;
/// DBManager
RCNConfigDBManager *_DBManager;
/// Current bundle identifier;
Expand Down Expand Up @@ -72,14 +77,17 @@ - (instancetype)initWithDBManager:(RCNConfigDBManager *)DBManager {
_activeConfig = [[NSMutableDictionary alloc] init];
_fetchedConfig = [[NSMutableDictionary alloc] init];
_defaultConfig = [[NSMutableDictionary alloc] init];
_activePersonalization = [[NSDictionary alloc] init];
_fetchedPersonalization = [[NSDictionary alloc] init];
_bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
if (!_bundleIdentifier) {
FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038",
@"Main bundle identifier is missing. Remote Config might not work properly.");
_bundleIdentifier = @"";
}
_DBManager = DBManager;
_configLoadFromDBSemaphore = dispatch_semaphore_create(0);
// Waits for both config and Personalization data to load.
_configLoadFromDBSemaphore = dispatch_semaphore_create(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a comment why the particular semaphore value is set. Also, maybe it will be a bit more readable if we call dispatch_semaphore_signal closer to the operation that requires it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the database methods closer.

[self loadConfigFromMainTable];
}
return self;
Expand All @@ -93,6 +101,44 @@ - (BOOL)initializationSuccessful {
return isDatabaseLoadSuccessful;
}

#pragma mark - database

/// This method is only meant to be called at init time. The underlying logic will need to be
/// revaluated if the assumption changes at a later time.
- (void)loadConfigFromMainTable {
if (!_DBManager) {
return;
}

NSAssert(!_isDatabaseLoadAlreadyInitiated, @"Database load has already been initiated");
_isDatabaseLoadAlreadyInitiated = true;

[_DBManager
loadMainWithBundleIdentifier:_bundleIdentifier
completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
NSDictionary *activeConfig, NSDictionary *defaultConfig) {
self->_fetchedConfig = [fetchedConfig mutableCopy];
self->_activeConfig = [activeConfig mutableCopy];
self->_defaultConfig = [defaultConfig mutableCopy];
dispatch_semaphore_signal(self->_configLoadFromDBSemaphore);
}];

[_DBManager loadPersonalizationWithCompletionHandler:^(
BOOL success, NSDictionary *fetchedPersonalization,
NSDictionary *activePersonalization, NSDictionary *defaultConfig) {
self->_fetchedPersonalization = [fetchedPersonalization copy];
self->_activePersonalization = [activePersonalization copy];
dispatch_semaphore_signal(self->_configLoadFromDBSemaphore);
}];
}

/// Update the current config result to main table.
/// @param values Values in a row to write to the table.
/// @param source The source the config data is coming from. It determines which table to write to.
- (void)updateMainTableWithValues:(NSArray *)values fromSource:(RCNDBSource)source {
[_DBManager insertMainTableWithValues:values fromSource:source completionHandler:nil];
}

#pragma mark - update
/// This function is for copying dictionary when user set up a default config or when user clicks
/// activate. For now the DBSource can only be Active or Default.
Expand Down Expand Up @@ -204,10 +250,17 @@ - (void)updateConfigContentWithResponse:(NSDictionary *)response
if ([state isEqualToString:RCNFetchResponseKeyStateUpdate]) {
[self handleUpdateStateForConfigNamespace:currentNamespace
withEntries:response[RCNFetchResponseKeyEntries]];
[self handleUpdatePersonalization:response[RCNFetchResponseKeyPersonalizationMetadata]];
return;
}
}

- (void)activatePersonalization {
_activePersonalization = _fetchedPersonalization;
[_DBManager insertOrUpdatePersonalizationConfig:_activePersonalization
fromSource:RCNDBSourceActive];
}

#pragma mark State handling
- (void)handleNoChangeStateForConfigNamespace:(NSString *)currentNamespace {
if (!_fetchedConfig[currentNamespace]) {
Expand Down Expand Up @@ -263,35 +316,14 @@ - (void)handleUpdateStateForConfigNamespace:(NSString *)currentNamespace
}
}

#pragma mark - database

/// This method is only meant to be called at init time. The underlying logic will need to be
/// revaluated if the assumption changes at a later time.
- (void)loadConfigFromMainTable {
if (!_DBManager) {
- (void)handleUpdatePersonalization:(NSDictionary *)metadata {
if (!metadata) {
return;
}

NSAssert(!_isDatabaseLoadAlreadyInitiated, @"Database load has already been initiated");
_isDatabaseLoadAlreadyInitiated = true;

[_DBManager
loadMainWithBundleIdentifier:_bundleIdentifier
completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
NSDictionary *activeConfig, NSDictionary *defaultConfig) {
self->_fetchedConfig = [fetchedConfig mutableCopy];
self->_activeConfig = [activeConfig mutableCopy];
self->_defaultConfig = [defaultConfig mutableCopy];
dispatch_semaphore_signal(self->_configLoadFromDBSemaphore);
}];
_fetchedPersonalization = metadata;
[_DBManager insertOrUpdatePersonalizationConfig:metadata fromSource:RCNDBSourceFetched];
}

/// Update the current config result to main table.
/// @param values Values in a row to write to the table.
/// @param source The source the config data is coming from. It determines which table to write to.
- (void)updateMainTableWithValues:(NSArray *)values fromSource:(RCNDBSource)source {
[_DBManager insertMainTableWithValues:values fromSource:source completionHandler:nil];
}
#pragma mark - getter/setter
- (NSDictionary *)fetchedConfig {
/// If this is the first time reading the fetchedConfig, we might still be reading it from the
Expand All @@ -314,6 +346,16 @@ - (NSDictionary *)defaultConfig {
return _defaultConfig;
}

- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace {
/// If this is the first time reading the active metadata, we might still be reading it from the
/// database.
[self checkAndWaitForInitialDatabaseLoad];
return @{
RCNFetchResponseKeyEntries : _activeConfig[FIRNamespace],
RCNFetchResponseKeyPersonalizationMetadata : _activePersonalization
};
}

/// We load the database async at init time. Block all further calls to active/fetched/default
/// configs until load is done.
/// @return Database load completion status.
Expand Down
7 changes: 7 additions & 0 deletions FirebaseRemoteConfig/Sources/RCNConfigDBManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ typedef void (^RCNDBLoadCompletion)(BOOL success,
/// Load experiment from experiment table.
/// @param handler The callback when reading from DB is complete.
- (void)loadExperimentWithCompletionHandler:(RCNDBCompletion)handler;
/// Load Personalization from table.
/// @param handler The callback when reading from DB is complete.
- (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler;

/// Insert a record in metadata table.
/// @param columnNameToValue The column name and its value to be inserted in metadata table.
Expand Down Expand Up @@ -100,6 +103,10 @@ typedef void (^RCNDBLoadCompletion)(BOOL success,
- (void)updateMetadataWithOption:(RCNUpdateOption)option
values:(NSArray *)values
completionHandler:(RCNDBCompletion)handler;

/// Insert or update the data in Personalization config.
- (BOOL)insertOrUpdatePersonalizationConfig:(NSDictionary *)metadata fromSource:(RCNDBSource)source;

/// Clear the record of given namespace and package name
/// before updating the table.
- (void)deleteRecordFromMainTableWithNamespace:(NSString *)namespace_p
Expand Down
Loading