Skip to content

Commit a9f3f35

Browse files
ryanwilsonwilhuff
authored andcommitted
Delete stale Firestore instances after FIRApp is deleted. (#809)
1 parent aa6f1ae commit a9f3f35

File tree

2 files changed

+96
-1
lines changed

2 files changed

+96
-1
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2018 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <FirebaseCore/FIRAppInternal.h>
18+
#import <FirebaseCore/FIROptionsInternal.h>
19+
#import <FirebaseFirestore/FIRFirestore.h>
20+
21+
#import <XCTest/XCTest.h>
22+
23+
@interface FIRFirestoreTests : XCTestCase
24+
@end
25+
26+
@implementation FIRFirestoreTests
27+
28+
- (void)testDeleteApp {
29+
// Create a FIRApp for testing.
30+
NSString *appName = @"custom_app_name";
31+
FIROptions *options =
32+
[[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:123ab" GCMSenderID:@"gcm_sender_id"];
33+
options.projectID = @"project_id";
34+
[FIRApp configureWithName:appName options:options];
35+
36+
// Ensure the app is set appropriately.
37+
FIRApp *app = [FIRApp appNamed:appName];
38+
FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
39+
XCTAssertEqualObjects(firestore.app, app);
40+
41+
// Ensure that firestoreForApp returns the same instance.
42+
XCTAssertEqualObjects(firestore, [FIRFirestore firestoreForApp:app]);
43+
44+
XCTestExpectation *defaultAppDeletedExpectation =
45+
[self expectationWithDescription:
46+
@"Deleting the default app should invalidate the default "
47+
@"Firestore instance."];
48+
[app deleteApp:^(BOOL success) {
49+
// Recreate the FIRApp with the same name, fetch a new Firestore instance and make sure it's
50+
// different than the other one.
51+
[FIRApp configureWithName:appName options:options];
52+
FIRApp *newApp = [FIRApp appNamed:appName];
53+
FIRFirestore *newInstance = [FIRFirestore firestoreForApp:newApp];
54+
XCTAssertNotEqualObjects(newInstance, firestore);
55+
56+
[defaultAppDeletedExpectation fulfill];
57+
}];
58+
59+
[self waitForExpectations:@[ defaultAppDeletedExpectation ] timeout:2];
60+
}
61+
62+
@end

Firestore/Source/API/FIRFirestore.mm

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
#import "FIRFirestore.h"
1818

19-
#import <FirebaseCore/FIRApp.h>
19+
#import <FirebaseCore/FIRAppInternal.h>
2020
#import <FirebaseCore/FIRLogger.h>
2121
#import <FirebaseCore/FIROptions.h>
2222

@@ -80,6 +80,36 @@ @implementation FIRFirestore {
8080
return instances;
8181
}
8282

83+
+ (void)initialize {
84+
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
85+
[center addObserverForName:kFIRAppDeleteNotification
86+
object:nil
87+
queue:nil
88+
usingBlock:^(NSNotification *_Nonnull note) {
89+
NSString *appName = note.userInfo[kFIRAppNameKey];
90+
if (appName == nil) return;
91+
92+
NSMutableDictionary *instances = [self instances];
93+
@synchronized(instances) {
94+
// Since the key for instances isn't just the app name, iterate over all the
95+
// keys to get the one(s) we have to delete. There could be multiple in case
96+
// the user calls firestoreForApp:database:.
97+
NSMutableArray *keysToDelete = [[NSMutableArray alloc] init];
98+
NSString *keyPrefix = [NSString stringWithFormat:@"%@|", appName];
99+
for (NSString *key in instances.allKeys) {
100+
if ([key hasPrefix:keyPrefix]) {
101+
[keysToDelete addObject:key];
102+
}
103+
}
104+
105+
// Loop through the keys found and delete them from the stored instances.
106+
for (NSString *key in keysToDelete) {
107+
[instances removeObjectForKey:key];
108+
}
109+
}
110+
}];
111+
}
112+
83113
+ (instancetype)firestore {
84114
FIRApp *app = [FIRApp defaultApp];
85115
if (!app) {
@@ -109,6 +139,9 @@ + (instancetype)firestoreForApp:(FIRApp *)app database:(NSString *)database {
109139
"database",
110140
util::WrapNSStringNoCopy(DatabaseId::kDefaultDatabaseId));
111141
}
142+
143+
// Note: If the key format changes, please change the code that detects FIRApps being deleted
144+
// contained in +initialize. It checks for the app's name followed by a | character.
112145
NSString *key = [NSString stringWithFormat:@"%@|%@", app.name, database];
113146

114147
NSMutableDictionary<NSString *, FIRFirestore *> *instances = self.instances;

0 commit comments

Comments
 (0)