From c6bde890ce2b352f86aa699b28f829d4cd85424c Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 15 Sep 2017 16:45:46 -0700 Subject: Adding Multi-Resource support to the Firebase iOS SDK (#278) * Adding Multi-Resource support to the Firebase iOS SDK. This CL also makes RepoInfo hashable and simplifies RepoManager based on this. --- Firebase/Database/Api/FIRDatabase.m | 95 ++++++++++++++++++++++++---------- Firebase/Database/Core/FRepoInfo.h | 6 ++- Firebase/Database/Core/FRepoInfo.m | 19 +++++++ Firebase/Database/Core/FRepoManager.m | 55 ++++++++++---------- Firebase/Database/Public/FIRDatabase.h | 21 ++++++++ 5 files changed, 142 insertions(+), 54 deletions(-) (limited to 'Firebase/Database') diff --git a/Firebase/Database/Api/FIRDatabase.m b/Firebase/Database/Api/FIRDatabase.m index 2dd77f6..4a8f5b8 100644 --- a/Firebase/Database/Api/FIRDatabase.m +++ b/Firebase/Database/Api/FIRDatabase.m @@ -36,6 +36,9 @@ @implementation FIRDatabase +/** A NSMutableDictionary of FirebaseApp name and FRepoInfo to FirebaseDatabase instance. */ +typedef NSMutableDictionary *> FIRDatabaseDictionary; + // The STR and STR_EXPAND macro allow a numeric version passed to he compiler driver // with a -D to be treated as a string instead of an invalid floating point value. #define STR(x) STR_EXPAND(x) @@ -51,13 +54,15 @@ static const char *FIREBASE_SEMVER = (const char *)STR(FIRDatabase_VERSION); NSString *appName = note.userInfo[kFIRAppNameKey]; if (appName == nil) { return; } - NSMutableDictionary *instances = [self instances]; + FIRDatabaseDictionary* instances = [self instances]; @synchronized (instances) { - FIRDatabase *deletedApp = instances[appName]; - if (deletedApp) { + NSMutableDictionary *databaseInstances = instances[appName]; + if (databaseInstances) { // Clean up the deleted instance in an effort to remove any resources still in use. // Note: Any leftover instances of this exact database will be invalid. - [FRepoManager disposeRepos:deletedApp.config]; + for (FIRDatabase * database in [databaseInstances allValues]) { + [FRepoManager disposeRepos:database.config]; + } [instances removeObjectForKey:appName]; } } @@ -65,16 +70,18 @@ static const char *FIREBASE_SEMVER = (const char *)STR(FIRDatabase_VERSION); } /** - * A static NSMutableDictionary of FirebaseApp names to FirebaseDatabase instance. To ensure thread- - * safety, it should only be accessed in databaseForApp, which is synchronized. + * A static NSMutableDictionary of FirebaseApp name and FRepoInfo to + * FirebaseDatabase instance. To ensure thread-safety, it should only be + * accessed in databaseForApp:URL:, which is synchronized. * * TODO: This serves a duplicate purpose as RepoManager. We should clean up. - * TODO: We should maybe be conscious of leaks and make this a weak map or similar - * but we have a lot of work to do to allow FirebaseDatabase/Repo etc. to be GC'd. + * TODO: We should maybe be conscious of leaks and make this a weak map or + * similar but we have a lot of work to do to allow FirebaseDatabase/Repo etc. + * to be GC'd. */ -+ (NSMutableDictionary *)instances { ++ (FIRDatabaseDictionary *)instances { static dispatch_once_t pred = 0; - static NSMutableDictionary *instances; + static FIRDatabaseDictionary *instances; dispatch_once(&pred, ^{ instances = [NSMutableDictionary dictionary]; }); @@ -92,27 +99,59 @@ static const char *FIREBASE_SEMVER = (const char *)STR(FIRDatabase_VERSION); return [FIRDatabase databaseForApp:app]; } ++ (FIRDatabase *)databaseWithURL:(NSString *)url { + FIRApp *app = [FIRApp defaultApp]; + if (app == nil) { + [NSException raise:@"FIRAppNotConfigured" + format:@"Failed to get default Firebase Database instance. " + @"Must call `[FIRApp configure]` (`FirebaseApp.configure()` in Swift) " + @"before using Firebase Database."]; + } + return [FIRDatabase databaseForApp:app URL:url]; +} + + (FIRDatabase *)databaseForApp:(FIRApp *)app { if (app == nil) { [NSException raise:@"InvalidFIRApp" format:@"nil FIRApp instance passed to databaseForApp."]; } - NSMutableDictionary *instances = [self instances]; - @synchronized (instances) { - FIRDatabase *database = instances[app.name]; - if (!database) { - NSString *databaseUrl = app.options.databaseURL; - if (databaseUrl == nil) { - [NSException raise:@"MissingDatabaseURL" format:@"Failed to get FIRDatabase instance: FIRApp object has no " - "databaseURL in its FirebaseOptions object."]; - } - FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl]; - if (![parsedUrl.path isEmpty]) { - [NSException raise:@"InvalidDatabaseURL" format:@"Configured Database URL '%@' is invalid. It should " - "point to the root of a Firebase Database but it includes a path: %@", - databaseUrl, [parsedUrl.path toString]]; - } + return [FIRDatabase databaseForApp:app URL:app.options.databaseURL]; +} ++ (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url { + if (app == nil) { + [NSException raise:@"InvalidFIRApp" + format:@"nil FIRApp instance passed to databaseForApp."]; + } + + if (url == nil) { + [NSException raise:@"MissingDatabaseURL" + format:@"Failed to get FirebaseDatabase instance: " + "Specify DatabaseURL within FIRApp or from your databaseForApp:URL: call."]; + } + + NSURL *databaseUrl = [NSURL URLWithString:url]; + + if (databaseUrl == nil) { + [NSException raise:@"InvalidDatabaseURL" format:@"The Database URL '%@' cannot be parsed. " + "Specify a valid DatabaseURL within FIRApp or from your databaseForApp:URL: call.", databaseUrl]; + } else if (![databaseUrl.path isEqualToString:@""] && ![databaseUrl.path isEqualToString:@"/"]) { + [NSException raise:@"InvalidDatabaseURL" format:@"Configured Database URL '%@' is invalid. It should point " + "to the root of a Firebase Database but it includes a path: %@",databaseUrl, databaseUrl.path]; + } + + FIRDatabaseDictionary *instances = [self instances]; + @synchronized (instances) { + NSMutableDictionary *urlInstanceMap = + instances[app.name]; + if (!urlInstanceMap) { + urlInstanceMap = [NSMutableDictionary dictionary]; + instances[app.name] = urlInstanceMap; + } + + FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl.absoluteString]; + FIRDatabase *database = urlInstanceMap[parsedUrl.repoInfo]; + if (!database) { id authTokenProvider = [FAuthTokenProvider authTokenProviderForApp:app]; // If this is the default app, don't set the session persistence key so that we use our @@ -125,8 +164,10 @@ static const char *FIREBASE_SEMVER = (const char *)STR(FIRDatabase_VERSION); FIRDatabaseConfig *config = [[FIRDatabaseConfig alloc] initWithSessionIdentifier:sessionIdentifier authTokenProvider:authTokenProvider]; - database = [[FIRDatabase alloc] initWithApp:app repoInfo:parsedUrl.repoInfo config:config]; - instances[app.name] = database; + database = [[FIRDatabase alloc] initWithApp:app + repoInfo:parsedUrl.repoInfo + config:config]; + urlInstanceMap[parsedUrl.repoInfo] = database; } return database; diff --git a/Firebase/Database/Core/FRepoInfo.h b/Firebase/Database/Core/FRepoInfo.h index dace937..433bf35 100644 --- a/Firebase/Database/Core/FRepoInfo.h +++ b/Firebase/Database/Core/FRepoInfo.h @@ -16,7 +16,7 @@ #import -@interface FRepoInfo : NSObject +@interface FRepoInfo : NSObject @property (nonatomic, readonly, strong) NSString* host; @property (nonatomic, readonly, strong) NSString* namespace; @@ -31,4 +31,8 @@ - (BOOL) isDemoHost; - (BOOL) isCustomHost; +- (id)copyWithZone:(NSZone *)zone; +- (NSUInteger)hash; +- (BOOL)isEqual:(id)anObject; + @end diff --git a/Firebase/Database/Core/FRepoInfo.m b/Firebase/Database/Core/FRepoInfo.m index 6b15fe5..925163e 100644 --- a/Firebase/Database/Core/FRepoInfo.m +++ b/Firebase/Database/Core/FRepoInfo.m @@ -112,4 +112,23 @@ return url; } +- (id)copyWithZone:(NSZone *)zone; { + return self; // Immutable +} + +- (NSUInteger)hash { + NSUInteger result = host.hash; + result = 31 * result + (secure ? 1 : 0); + result = 31 * result + namespace.hash; + result = 31 * result + host.hash; + return result; +} + +- (BOOL)isEqual:(id)anObject { + if (![anObject isKindOfClass:[FRepoInfo class]]) return NO; + FRepoInfo *other = (FRepoInfo *)anObject; + return secure == other.secure && [host isEqualToString:other.host] && + [namespace isEqualToString:other.namespace]; +} + @end diff --git a/Firebase/Database/Core/FRepoManager.m b/Firebase/Database/Core/FRepoManager.m index 6a134d2..c5194d5 100644 --- a/Firebase/Database/Core/FRepoManager.m +++ b/Firebase/Database/Core/FRepoManager.m @@ -24,9 +24,13 @@ @implementation FRepoManager -+ (NSMutableDictionary *)configs { +typedef NSMutableDictionary *> + FRepoDictionary; + ++ (FRepoDictionary *)configs { static dispatch_once_t pred = 0; - static NSMutableDictionary *configs; + static FRepoDictionary *configs; dispatch_once(&pred, ^{ configs = [NSMutableDictionary dictionary]; }); @@ -39,33 +43,32 @@ */ + (FRepo *) getRepo:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config { [config freeze]; - NSString* repoHashString = [NSString stringWithFormat:@"%@_%@", repoInfo.host, repoInfo.namespace]; - NSMutableDictionary *configs = [FRepoManager configs]; + FRepoDictionary *configs = [FRepoManager configs]; @synchronized(configs) { - NSMutableDictionary *repos = configs[config.sessionIdentifier]; - if (!repos || repos[repoHashString] == nil) { + NSMutableDictionary *repos = configs[config.sessionIdentifier]; + if (!repos || repos[repoInfo] == nil) { // Calling this should create the repo. [FIRDatabase createDatabaseForTests:repoInfo config:config]; } - return configs[config.sessionIdentifier][repoHashString]; + return configs[config.sessionIdentifier][repoInfo]; } } + (FRepo *) createRepo:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config database:(FIRDatabase *)database { [config freeze]; - NSString* repoHashString = [NSString stringWithFormat:@"%@_%@", repoInfo.host, repoInfo.namespace]; - NSMutableDictionary *configs = [FRepoManager configs]; + FRepoDictionary *configs = [FRepoManager configs]; @synchronized(configs) { - NSMutableDictionary *repos = configs[config.sessionIdentifier]; + NSMutableDictionary *repos = + configs[config.sessionIdentifier]; if (!repos) { repos = [NSMutableDictionary dictionary]; configs[config.sessionIdentifier] = repos; } - FRepo *repo = repos[repoHashString]; + FRepo *repo = repos[repoInfo]; if (repo == nil) { repo = [[FRepo alloc] initWithRepoInfo:repoInfo config:config database:database]; - repos[repoHashString] = repo; + repos[repoInfo] = repo; return repo; } else { [NSException raise:@"RepoExists" format:@"createRepo called for Repo that already exists."]; @@ -76,9 +79,9 @@ + (void) interrupt:(FIRDatabaseConfig *)config { dispatch_async([FIRDatabaseQuery sharedQueue], ^{ - NSMutableDictionary *configs = [FRepoManager configs]; - NSMutableDictionary *repos = configs[config.sessionIdentifier]; - for (FRepo* repo in [repos allValues]) { + FRepoDictionary *configs = [FRepoManager configs]; + NSMutableDictionary *repos = configs[config.sessionIdentifier]; + for (FRepo *repo in [repos allValues]) { [repo interrupt]; } }); @@ -86,20 +89,20 @@ + (void) interruptAll { dispatch_async([FIRDatabaseQuery sharedQueue], ^{ - NSMutableDictionary *configs = [FRepoManager configs]; - for (NSMutableDictionary *repos in [configs allValues]) { - for (FRepo* repo in [repos allValues]) { - [repo interrupt]; - } + FRepoDictionary *configs = [FRepoManager configs]; + for (NSMutableDictionary *repos in [configs allValues]) { + for (FRepo *repo in [repos allValues]) { + [repo interrupt]; + } } }); } + (void) resume:(FIRDatabaseConfig *)config { dispatch_async([FIRDatabaseQuery sharedQueue], ^{ - NSMutableDictionary *configs = [FRepoManager configs]; - NSMutableDictionary *repos = configs[config.sessionIdentifier]; - for (FRepo* repo in [repos allValues]) { + FRepoDictionary *configs = [FRepoManager configs]; + NSMutableDictionary *repos = configs[config.sessionIdentifier]; + for (FRepo *repo in [repos allValues]) { [repo resume]; } }); @@ -107,9 +110,9 @@ + (void) resumeAll { dispatch_async([FIRDatabaseQuery sharedQueue], ^{ - NSMutableDictionary *configs = [FRepoManager configs]; - for (NSMutableDictionary *repos in [configs allValues]) { - for (FRepo* repo in [repos allValues]) { + FRepoDictionary *configs = [FRepoManager configs]; + for (NSMutableDictionary *repos in [configs allValues]) { + for (FRepo *repo in [repos allValues]) { [repo resume]; } } diff --git a/Firebase/Database/Public/FIRDatabase.h b/Firebase/Database/Public/FIRDatabase.h index a67f96d..606f164 100644 --- a/Firebase/Database/Public/FIRDatabase.h +++ b/Firebase/Database/Public/FIRDatabase.h @@ -45,6 +45,27 @@ FIR_SWIFT_NAME(Database) */ + (FIRDatabase *) database FIR_SWIFT_NAME(database()); +/** + * Gets a FirebaseDatabase instance for the specified URL. + * + * @param url The URL to the Firebase Database instance you want to access. + * @return A FIRDatabase instance. + */ ++ (FIRDatabase *)databaseWithURL:(NSString *)url NS_SWIFT_NAME(database(url:)); + +/** + * Gets a FirebaseDatabase instance for the specified URL, using the specified + * FirebaseApp. + * + * @param app The FIRApp to get a FIRDatabase for. + * @param url The URL to the Firebase Database instance you want to access. + * @return A FIRDatabase instance. + */ +// clang-format off ++ (FIRDatabase *)databaseForApp:(FIRApp *)app + URL:(NSString *)url NS_SWIFT_NAME(database(app:url:)); +// clang-format on + /** * Gets an instance of FIRDatabase for a specific FIRApp. * -- cgit v1.2.3