2323
2424NS_ASSUME_NONNULL_BEGIN
2525
26- @interface FIRComponentContainer ()
26+ @interface FIRComponentContainer () {
27+ dispatch_queue_t _containerQueue;
28+ }
2729
2830// / The dictionary of components that are registered for a particular app. The key is an NSString
2931// / of the protocol.
@@ -67,6 +69,8 @@ - (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet<Class> *)all
6769 _app = app;
6870 _cachedInstances = [NSMutableDictionary <NSString *, id > dictionary];
6971 _components = [NSMutableDictionary <NSString *, FIRComponentCreationBlock> dictionary];
72+ _containerQueue =
73+ dispatch_queue_create (" com.google.FirebaseComponentContainer" , DISPATCH_QUEUE_SERIAL);
7074
7175 [self populateComponentsFromRegisteredClasses: allRegistrants forApp: app];
7276 }
@@ -92,7 +96,7 @@ - (void)populateComponentsFromRegisteredClasses:(NSSet<Class> *)classes forApp:(
9296 // Store the creation block for later usage.
9397 self.components [protocolName] = component.creationBlock ;
9498
95- // Instantiate the
99+ // Instantiate the instance if it has requested to be instantiated.
96100 BOOL shouldInstantiateEager =
97101 (component.instantiationTiming == FIRInstantiationTimingAlwaysEager);
98102 BOOL shouldInstantiateDefaultEager =
@@ -136,7 +140,9 @@ - (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol
136140
137141 // The instance is ready to be returned, but check if it should be cached first before returning.
138142 if (shouldCache) {
139- self.cachedInstances [protocolName] = instance;
143+ dispatch_sync (_containerQueue, ^{
144+ self.cachedInstances [protocolName] = instance;
145+ });
140146 }
141147
142148 return instance;
@@ -147,7 +153,11 @@ - (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol
147153- (nullable id )instanceForProtocol : (Protocol *)protocol {
148154 // Check if there is a cached instance, and return it if so.
149155 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+
151161 if (cachedInstance) {
152162 return cachedInstance;
153163 }
@@ -161,14 +171,29 @@ - (nullable id)instanceForProtocol:(Protocol *)protocol {
161171
162172- (void )removeAllCachedInstances {
163173 // 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 ) {
165185 if ([instance conformsToProtocol: @protocol (FIRComponentLifecycleMaintainer)] &&
166186 [instance respondsToSelector: @selector (appWillBeDeleted: )]) {
167187 [instance appWillBeDeleted: self .app];
168188 }
169189 }
170190
171- [self .cachedInstances removeAllObjects ];
191+ instancesCopy = nil ;
192+
193+ // Empty the cache.
194+ dispatch_sync (_containerQueue, ^{
195+ [self .cachedInstances removeAllObjects ];
196+ });
172197}
173198
174199@end
0 commit comments