added iOS source code
[wl-app.git] / iOS / Pods / FirebaseCore / Firebase / Core / FIRComponentContainer.m
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 "Private/FIRComponentContainer.h"
18
19 #import "Private/FIRAppInternal.h"
20 #import "Private/FIRComponent.h"
21 #import "Private/FIRComponentRegistrant.h"
22 #import "Private/FIRLogger.h"
23
24 NS_ASSUME_NONNULL_BEGIN
25
26 @interface FIRComponentContainer ()
27
28 /// The dictionary of components that are registered for a particular app. The key is an NSString
29 /// of the protocol.
30 @property(nonatomic, strong) NSMutableDictionary<NSString *, FIRComponentCreationBlock> *components;
31
32 /// Cached instances of components that requested to be cached.
33 @property(nonatomic, strong) NSMutableDictionary<NSString *, id> *cachedInstances;
34
35 @end
36
37 @implementation FIRComponentContainer
38
39 // Collection of all classes that register to provide components.
40 static NSMutableSet<Class> *gFIRComponentRegistrants;
41
42 #pragma mark - Public Registration
43
44 + (void)registerAsComponentRegistrant:(Class)klass {
45   static dispatch_once_t onceToken;
46   dispatch_once(&onceToken, ^{
47     gFIRComponentRegistrants = [[NSMutableSet<Class> alloc] init];
48   });
49
50   [self registerAsComponentRegistrant:klass inSet:gFIRComponentRegistrants];
51 }
52
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.");
58     return;
59   }
60
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
65                 format:
66                     @"Class %@ attempted to register components, but it does not conform to "
67                     @"`FIRComponentRegistrant` or provide a `componentsToRegister:` method.",
68                     klass];
69   }
70
71   [allRegistrants addObject:klass];
72 }
73
74 #pragma mark - Internal Initialization
75
76 - (instancetype)initWithApp:(FIRApp *)app {
77   return [self initWithApp:app registrants:gFIRComponentRegistrants];
78 }
79
80 - (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet<Class> *)allRegistrants {
81   self = [super init];
82   if (self) {
83     _app = app;
84     _cachedInstances = [NSMutableDictionary<NSString *, id> dictionary];
85     _components = [NSMutableDictionary<NSString *, FIRComponentCreationBlock> dictionary];
86
87     [self populateComponentsFromRegisteredClasses:allRegistrants forApp:app];
88   }
89   return self;
90 }
91
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
97     // protocol.
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.",
104                     protocolName);
105         continue;
106       }
107
108       // Store the creation block for later usage.
109       self.components[protocolName] = component.creationBlock;
110
111       // Instantiate the
112       BOOL shouldInstantiateEager =
113           (component.instantiationTiming == FIRInstantiationTimingAlwaysEager);
114       BOOL shouldInstantiateDefaultEager =
115           (component.instantiationTiming == FIRInstantiationTimingEagerInDefaultApp &&
116            [app isDefaultApp]);
117       if (shouldInstantiateEager || shouldInstantiateDefaultEager) {
118         [self instantiateInstanceForProtocol:component.protocol withBlock:component.creationBlock];
119       }
120     }
121   }
122 }
123
124 #pragma mark - Instance Creation
125
126 /// Instantiate an instance of a class that conforms to the specified protocol.
127 /// This will:
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) {
134     return nil;
135   }
136
137   // Create an instance using the creation block.
138   BOOL shouldCache = NO;
139   id instance = creationBlock(self, &shouldCache);
140   if (!instance) {
141     return nil;
142   }
143
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",
150                 protocolName);
151   }
152
153   // The instance is ready to be returned, but check if it should be cached first before returning.
154   if (shouldCache) {
155     self.cachedInstances[protocolName] = instance;
156   }
157
158   return instance;
159 }
160
161 #pragma mark - Internal Retrieval
162
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;
169   }
170
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];
174 }
175
176 #pragma mark - Lifecycle
177
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];
184     }
185   }
186
187   [self.cachedInstances removeAllObjects];
188 }
189
190 #pragma mark - Testing Initializers
191
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]];
197   if (self) {
198     _components = [components mutableCopy];
199   }
200   return self;
201 }
202
203 @end
204
205 NS_ASSUME_NONNULL_END