2 * Copyright 2018 Google
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #import "Private/FIRComponentContainer.h"
19 #import "Private/FIRAppInternal.h"
20 #import "Private/FIRComponent.h"
21 #import "Private/FIRComponentRegistrant.h"
22 #import "Private/FIRLogger.h"
24 NS_ASSUME_NONNULL_BEGIN
26 @interface FIRComponentContainer ()
28 /// The dictionary of components that are registered for a particular app. The key is an NSString
30 @property(nonatomic, strong) NSMutableDictionary<NSString *, FIRComponentCreationBlock> *components;
32 /// Cached instances of components that requested to be cached.
33 @property(nonatomic, strong) NSMutableDictionary<NSString *, id> *cachedInstances;
37 @implementation FIRComponentContainer
39 // Collection of all classes that register to provide components.
40 static NSMutableSet<Class> *gFIRComponentRegistrants;
42 #pragma mark - Public Registration
44 + (void)registerAsComponentRegistrant:(Class)klass {
45 static dispatch_once_t onceToken;
46 dispatch_once(&onceToken, ^{
47 gFIRComponentRegistrants = [[NSMutableSet<Class> alloc] init];
50 [self registerAsComponentRegistrant:klass inSet:gFIRComponentRegistrants];
53 + (void)registerAsComponentRegistrant:(Class)klass inSet:(NSMutableSet<Class> *)allRegistrants {
54 // Validate the array to store the components is initialized.
55 if (!allRegistrants) {
56 FIRLogWarning(kFIRLoggerCore, @"I-COR000025",
57 @"Attempted to store registered components in an empty set.");
61 // Ensure the class given conforms to the proper protocol.
62 if (![klass conformsToProtocol:@protocol(FIRComponentRegistrant)] ||
63 ![klass respondsToSelector:@selector(componentsToRegister)]) {
64 [NSException raise:NSInvalidArgumentException
66 @"Class %@ attempted to register components, but it does not conform to "
67 @"`FIRComponentRegistrant` or provide a `componentsToRegister:` method.",
71 [allRegistrants addObject:klass];
74 #pragma mark - Internal Initialization
76 - (instancetype)initWithApp:(FIRApp *)app {
77 return [self initWithApp:app registrants:gFIRComponentRegistrants];
80 - (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet<Class> *)allRegistrants {
84 _cachedInstances = [NSMutableDictionary<NSString *, id> dictionary];
85 _components = [NSMutableDictionary<NSString *, FIRComponentCreationBlock> dictionary];
87 [self populateComponentsFromRegisteredClasses:allRegistrants forApp:app];
92 - (void)populateComponentsFromRegisteredClasses:(NSSet<Class> *)classes forApp:(FIRApp *)app {
93 // Loop through the verified component registrants and populate the components array.
94 for (Class<FIRComponentRegistrant> klass in classes) {
95 // Loop through all the components being registered and store them as appropriate.
96 // Classes which do not provide functionality should use a dummy FIRComponentRegistrant
98 for (FIRComponent *component in [klass componentsToRegister]) {
99 // Check if the component has been registered before, and error out if so.
100 NSString *protocolName = NSStringFromProtocol(component.protocol);
101 if (self.components[protocolName]) {
102 FIRLogError(kFIRLoggerCore, @"I-COR000029",
103 @"Attempted to register protocol %@, but it already has an implementation.",
108 // Store the creation block for later usage.
109 self.components[protocolName] = component.creationBlock;
112 BOOL shouldInstantiateEager =
113 (component.instantiationTiming == FIRInstantiationTimingAlwaysEager);
114 BOOL shouldInstantiateDefaultEager =
115 (component.instantiationTiming == FIRInstantiationTimingEagerInDefaultApp &&
117 if (shouldInstantiateEager || shouldInstantiateDefaultEager) {
118 [self instantiateInstanceForProtocol:component.protocol withBlock:component.creationBlock];
124 #pragma mark - Instance Creation
126 /// Instantiate an instance of a class that conforms to the specified protocol.
128 /// - Call the block to create an instance if possible,
129 /// - Validate that the instance returned conforms to the protocol it claims to,
130 /// - Cache the instance if the block requests it
131 - (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol
132 withBlock:(FIRComponentCreationBlock)creationBlock {
133 if (!creationBlock) {
137 // Create an instance using the creation block.
138 BOOL shouldCache = NO;
139 id instance = creationBlock(self, &shouldCache);
144 // An instance was created, validate that it conforms to the protocol it claims to.
145 NSString *protocolName = NSStringFromProtocol(protocol);
146 if (![instance conformsToProtocol:protocol]) {
147 FIRLogError(kFIRLoggerCore, @"I-COR000030",
148 @"An instance conforming to %@ was requested, but the instance provided does not "
149 @"conform to the protocol",
153 // The instance is ready to be returned, but check if it should be cached first before returning.
155 self.cachedInstances[protocolName] = instance;
161 #pragma mark - Internal Retrieval
163 - (nullable id)instanceForProtocol:(Protocol *)protocol {
164 // Check if there is a cached instance, and return it if so.
165 NSString *protocolName = NSStringFromProtocol(protocol);
166 id cachedInstance = self.cachedInstances[protocolName];
167 if (cachedInstance) {
168 return cachedInstance;
171 // Use the creation block to instantiate an instance and return it.
172 FIRComponentCreationBlock creationBlock = self.components[protocolName];
173 return [self instantiateInstanceForProtocol:protocol withBlock:creationBlock];
176 #pragma mark - Lifecycle
178 - (void)removeAllCachedInstances {
179 // Loop through the cache and notify each instance that is a maintainer to clean up after itself.
180 for (id instance in self.cachedInstances.allValues) {
181 if ([instance conformsToProtocol:@protocol(FIRComponentLifecycleMaintainer)] &&
182 [instance respondsToSelector:@selector(appWillBeDeleted:)]) {
183 [instance appWillBeDeleted:self.app];
187 [self.cachedInstances removeAllObjects];
190 #pragma mark - Testing Initializers
192 // TODO(wilsonryan): Set up a testing flag so this only is compiled in with unit tests.
193 /// Initialize an instance with an app and existing components.
194 - (instancetype)initWithApp:(FIRApp *)app
195 components:(NSDictionary<NSString *, FIRComponentCreationBlock> *)components {
196 self = [self initWithApp:app registrants:[[NSMutableSet alloc] init]];
198 _components = [components mutableCopy];
205 NS_ASSUME_NONNULL_END