23
23
24
24
NS_ASSUME_NONNULL_BEGIN
25
25
26
- @interface FIRComponentContainer ()
26
+ @interface FIRComponentContainer () {
27
+ dispatch_queue_t _containerQueue;
28
+ }
27
29
28
30
// / The dictionary of components that are registered for a particular app. The key is an NSString
29
31
// / of the protocol.
@@ -67,6 +69,8 @@ - (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet<Class> *)all
67
69
_app = app;
68
70
_cachedInstances = [NSMutableDictionary <NSString *, id > dictionary];
69
71
_components = [NSMutableDictionary <NSString *, FIRComponentCreationBlock> dictionary];
72
+ _containerQueue =
73
+ dispatch_queue_create (" com.google.FirebaseComponentContainer" , DISPATCH_QUEUE_SERIAL);
70
74
71
75
[self populateComponentsFromRegisteredClasses: allRegistrants forApp: app];
72
76
}
@@ -92,7 +96,7 @@ - (void)populateComponentsFromRegisteredClasses:(NSSet<Class> *)classes forApp:(
92
96
// Store the creation block for later usage.
93
97
self.components [protocolName] = component.creationBlock ;
94
98
95
- // Instantiate the
99
+ // Instantiate the instance if it has requested to be instantiated.
96
100
BOOL shouldInstantiateEager =
97
101
(component.instantiationTiming == FIRInstantiationTimingAlwaysEager);
98
102
BOOL shouldInstantiateDefaultEager =
@@ -136,7 +140,9 @@ - (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol
136
140
137
141
// The instance is ready to be returned, but check if it should be cached first before returning.
138
142
if (shouldCache) {
139
- self.cachedInstances [protocolName] = instance;
143
+ dispatch_sync (_containerQueue, ^{
144
+ self.cachedInstances [protocolName] = instance;
145
+ });
140
146
}
141
147
142
148
return instance;
@@ -147,7 +153,11 @@ - (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol
147
153
- (nullable id )instanceForProtocol : (Protocol *)protocol {
148
154
// Check if there is a cached instance, and return it if so.
149
155
NSString *protocolName = NSStringFromProtocol (protocol);
150
- id cachedInstance = self.cachedInstances [protocolName];
156
+ __block id cachedInstance;
157
+ dispatch_sync (_containerQueue, ^{
158
+ cachedInstance = self.cachedInstances [protocolName];
159
+ });
160
+
151
161
if (cachedInstance) {
152
162
return cachedInstance;
153
163
}
@@ -161,14 +171,29 @@ - (nullable id)instanceForProtocol:(Protocol *)protocol {
161
171
162
172
- (void )removeAllCachedInstances {
163
173
// Loop through the cache and notify each instance that is a maintainer to clean up after itself.
164
- for (id instance in self.cachedInstances .allValues ) {
174
+ // Design note: we're getting a copy here, unlocking the cached instances, iterating over the
175
+ // copy, then locking and removing all cached instances. A race condition *could* exist where a
176
+ // new cached instance is created between the copy and the removal, but the chances are slim and
177
+ // side-effects are significantly smaller than including the entire loop in the `dispatch_sync`
178
+ // block (access to the cache from inside the block would deadlock and crash).
179
+ __block NSDictionary <NSString *, id > *instancesCopy;
180
+ dispatch_sync (_containerQueue, ^{
181
+ instancesCopy = [self .cachedInstances copy ];
182
+ });
183
+
184
+ for (id instance in instancesCopy.allValues ) {
165
185
if ([instance conformsToProtocol: @protocol (FIRComponentLifecycleMaintainer)] &&
166
186
[instance respondsToSelector: @selector (appWillBeDeleted: )]) {
167
187
[instance appWillBeDeleted: self .app];
168
188
}
169
189
}
170
190
171
- [self .cachedInstances removeAllObjects ];
191
+ instancesCopy = nil ;
192
+
193
+ // Empty the cache.
194
+ dispatch_sync (_containerQueue, ^{
195
+ [self .cachedInstances removeAllObjects ];
196
+ });
172
197
}
173
198
174
199
@end
0 commit comments