aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Ryan Wilson <wilsonryan@google.com>2018-02-16 17:03:14 -0500
committerGravatar Gil <mcg@google.com>2018-02-16 14:03:14 -0800
commita9f3f35d483f1031ef2e2860aeda921f56e1bf08 (patch)
tree6df1a5ae7487f28af10b783935e914b70e5197b3
parentaa6f1ae0993ed948fb0b39283561bb3321eea5e9 (diff)
Delete stale Firestore instances after FIRApp is deleted. (#809)
-rw-r--r--Firestore/Example/Tests/API/FIRFirestoreTests.mm62
-rw-r--r--Firestore/Source/API/FIRFirestore.mm35
2 files changed, 96 insertions, 1 deletions
diff --git a/Firestore/Example/Tests/API/FIRFirestoreTests.mm b/Firestore/Example/Tests/API/FIRFirestoreTests.mm
new file mode 100644
index 0000000..4daf35a
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRFirestoreTests.mm
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <FirebaseCore/FIRAppInternal.h>
+#import <FirebaseCore/FIROptionsInternal.h>
+#import <FirebaseFirestore/FIRFirestore.h>
+
+#import <XCTest/XCTest.h>
+
+@interface FIRFirestoreTests : XCTestCase
+@end
+
+@implementation FIRFirestoreTests
+
+- (void)testDeleteApp {
+ // Create a FIRApp for testing.
+ NSString *appName = @"custom_app_name";
+ FIROptions *options =
+ [[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:123ab" GCMSenderID:@"gcm_sender_id"];
+ options.projectID = @"project_id";
+ [FIRApp configureWithName:appName options:options];
+
+ // Ensure the app is set appropriately.
+ FIRApp *app = [FIRApp appNamed:appName];
+ FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
+ XCTAssertEqualObjects(firestore.app, app);
+
+ // Ensure that firestoreForApp returns the same instance.
+ XCTAssertEqualObjects(firestore, [FIRFirestore firestoreForApp:app]);
+
+ XCTestExpectation *defaultAppDeletedExpectation =
+ [self expectationWithDescription:
+ @"Deleting the default app should invalidate the default "
+ @"Firestore instance."];
+ [app deleteApp:^(BOOL success) {
+ // Recreate the FIRApp with the same name, fetch a new Firestore instance and make sure it's
+ // different than the other one.
+ [FIRApp configureWithName:appName options:options];
+ FIRApp *newApp = [FIRApp appNamed:appName];
+ FIRFirestore *newInstance = [FIRFirestore firestoreForApp:newApp];
+ XCTAssertNotEqualObjects(newInstance, firestore);
+
+ [defaultAppDeletedExpectation fulfill];
+ }];
+
+ [self waitForExpectations:@[ defaultAppDeletedExpectation ] timeout:2];
+}
+
+@end
diff --git a/Firestore/Source/API/FIRFirestore.mm b/Firestore/Source/API/FIRFirestore.mm
index 6d2d27b..5a50710 100644
--- a/Firestore/Source/API/FIRFirestore.mm
+++ b/Firestore/Source/API/FIRFirestore.mm
@@ -16,7 +16,7 @@
#import "FIRFirestore.h"
-#import <FirebaseCore/FIRApp.h>
+#import <FirebaseCore/FIRAppInternal.h>
#import <FirebaseCore/FIRLogger.h>
#import <FirebaseCore/FIROptions.h>
@@ -80,6 +80,36 @@ extern "C" NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
return instances;
}
++ (void)initialize {
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ [center addObserverForName:kFIRAppDeleteNotification
+ object:nil
+ queue:nil
+ usingBlock:^(NSNotification *_Nonnull note) {
+ NSString *appName = note.userInfo[kFIRAppNameKey];
+ if (appName == nil) return;
+
+ NSMutableDictionary *instances = [self instances];
+ @synchronized(instances) {
+ // Since the key for instances isn't just the app name, iterate over all the
+ // keys to get the one(s) we have to delete. There could be multiple in case
+ // the user calls firestoreForApp:database:.
+ NSMutableArray *keysToDelete = [[NSMutableArray alloc] init];
+ NSString *keyPrefix = [NSString stringWithFormat:@"%@|", appName];
+ for (NSString *key in instances.allKeys) {
+ if ([key hasPrefix:keyPrefix]) {
+ [keysToDelete addObject:key];
+ }
+ }
+
+ // Loop through the keys found and delete them from the stored instances.
+ for (NSString *key in keysToDelete) {
+ [instances removeObjectForKey:key];
+ }
+ }
+ }];
+}
+
+ (instancetype)firestore {
FIRApp *app = [FIRApp defaultApp];
if (!app) {
@@ -109,6 +139,9 @@ extern "C" NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
"database",
util::WrapNSStringNoCopy(DatabaseId::kDefaultDatabaseId));
}
+
+ // Note: If the key format changes, please change the code that detects FIRApps being deleted
+ // contained in +initialize. It checks for the app's name followed by a | character.
NSString *key = [NSString stringWithFormat:@"%@|%@", app.name, database];
NSMutableDictionary<NSString *, FIRFirestore *> *instances = self.instances;