aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Core/FIRComponentContainer.m
blob: 381c95c43150119c8eae146947deb2537fcfefff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/*
 * Copyright 2018 Google
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "Private/FIRComponentContainer.h"

#import "Private/FIRAppInternal.h"
#import "Private/FIRComponent.h"
#import "Private/FIRComponentRegistrant.h"
#import "Private/FIRLogger.h"

NS_ASSUME_NONNULL_BEGIN

@interface FIRComponentContainer ()

/// The dictionary of components that are registered for a particular app. The key is an NSString
/// of the protocol.
@property(nonatomic, strong) NSMutableDictionary<NSString *, FIRComponentCreationBlock> *components;

/// Cached instances of components that requested to be cached.
@property(nonatomic, strong) NSMutableDictionary<NSString *, id> *cachedInstances;

@end

@implementation FIRComponentContainer

// Collection of all classes that register to provide components.
static NSMutableSet<Class> *gFIRComponentRegistrants;

#pragma mark - Public Registration

+ (void)registerAsComponentRegistrant:(Class)klass {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    gFIRComponentRegistrants = [[NSMutableSet<Class> alloc] init];
  });

  [self registerAsComponentRegistrant:klass inSet:gFIRComponentRegistrants];
}

+ (void)registerAsComponentRegistrant:(Class)klass inSet:(NSMutableSet<Class> *)allRegistrants {
  // Validate the array to store the components is initialized.
  if (!allRegistrants) {
    FIRLogWarning(kFIRLoggerCore, @"I-COR000025",
                  @"Attempted to store registered components in an empty set.");
    return;
  }

  // Ensure the class given conforms to the proper protocol.
  if (![klass conformsToProtocol:@protocol(FIRComponentRegistrant)] ||
      ![klass respondsToSelector:@selector(componentsToRegister)]) {
    [NSException raise:NSInvalidArgumentException
                format:
                    @"Class %@ attempted to register components, but it does not conform to "
                    @"`FIRComponentRegistrant` or provide a `componentsToRegister:` method.",
                    klass];
  }

  [allRegistrants addObject:klass];
}

#pragma mark - Internal Initialization

- (instancetype)initWithApp:(FIRApp *)app {
  return [self initWithApp:app registrants:gFIRComponentRegistrants];
}

- (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet<Class> *)allRegistrants {
  self = [super init];
  if (self) {
    _app = app;
    _cachedInstances = [NSMutableDictionary<NSString *, id> dictionary];
    _components = [NSMutableDictionary<NSString *, FIRComponentCreationBlock> dictionary];

    [self populateComponentsFromRegisteredClasses:allRegistrants forApp:app];
  }
  return self;
}

- (void)populateComponentsFromRegisteredClasses:(NSSet<Class> *)classes forApp:(FIRApp *)app {
  // Loop through the verified component registrants and populate the components array.
  for (Class<FIRComponentRegistrant> klass in classes) {
    // Loop through all the components being registered and store them as appropriate.
    // Classes which do not provide functionality should use a dummy FIRComponentRegistrant
    // protocol.
    for (FIRComponent *component in [klass componentsToRegister]) {
      // Check if the component has been registered before, and error out if so.
      NSString *protocolName = NSStringFromProtocol(component.protocol);
      if (self.components[protocolName]) {
        FIRLogError(kFIRLoggerCore, @"I-COR000029",
                    @"Attempted to register protocol %@, but it already has an implementation.",
                    protocolName);
        continue;
      }

      // Store the creation block for later usage.
      self.components[protocolName] = component.creationBlock;

      // Instantiate the
      BOOL shouldInstantiateEager =
          (component.instantiationTiming == FIRInstantiationTimingAlwaysEager);
      BOOL shouldInstantiateDefaultEager =
          (component.instantiationTiming == FIRInstantiationTimingEagerInDefaultApp &&
           [app isDefaultApp]);
      if (shouldInstantiateEager || shouldInstantiateDefaultEager) {
        [self instantiateInstanceForProtocol:component.protocol withBlock:component.creationBlock];
      }
    }
  }
}

#pragma mark - Instance Creation

/// Instantiate an instance of a class that conforms to the specified protocol.
/// This will:
///   - Call the block to create an instance if possible,
///   - Validate that the instance returned conforms to the protocol it claims to,
///   - Cache the instance if the block requests it
- (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol
                                    withBlock:(FIRComponentCreationBlock)creationBlock {
  if (!creationBlock) {
    return nil;
  }

  // Create an instance using the creation block.
  BOOL shouldCache = NO;
  id instance = creationBlock(self, &shouldCache);
  if (!instance) {
    return nil;
  }

  // An instance was created, validate that it conforms to the protocol it claims to.
  NSString *protocolName = NSStringFromProtocol(protocol);
  if (![instance conformsToProtocol:protocol]) {
    FIRLogError(kFIRLoggerCore, @"I-COR000030",
                @"An instance conforming to %@ was requested, but the instance provided does not "
                @"conform to the protocol",
                protocolName);
  }

  // The instance is ready to be returned, but check if it should be cached first before returning.
  if (shouldCache) {
    self.cachedInstances[protocolName] = instance;
  }

  return instance;
}

#pragma mark - Internal Retrieval

- (nullable id)instanceForProtocol:(Protocol *)protocol {
  // Check if there is a cached instance, and return it if so.
  NSString *protocolName = NSStringFromProtocol(protocol);
  id cachedInstance = self.cachedInstances[protocolName];
  if (cachedInstance) {
    return cachedInstance;
  }

  // Use the creation block to instantiate an instance and return it.
  FIRComponentCreationBlock creationBlock = self.components[protocolName];
  return [self instantiateInstanceForProtocol:protocol withBlock:creationBlock];
}

#pragma mark - Lifecycle

- (void)removeAllCachedInstances {
  // Loop through the cache and notify each instance that is a maintainer to clean up after itself.
  for (id instance in self.cachedInstances.allValues) {
    if ([instance conformsToProtocol:@protocol(FIRComponentLifecycleMaintainer)] &&
        [instance respondsToSelector:@selector(appWillBeDeleted:)]) {
      [instance appWillBeDeleted:self.app];
    }
  }

  [self.cachedInstances removeAllObjects];
}

#pragma mark - Testing Initializers

// TODO(wilsonryan): Set up a testing flag so this only is compiled in with unit tests.
/// Initialize an instance with an app and existing components.
- (instancetype)initWithApp:(FIRApp *)app
                 components:(NSDictionary<NSString *, FIRComponentCreationBlock> *)components {
  self = [self initWithApp:app registrants:[[NSMutableSet alloc] init]];
  if (self) {
    _components = [components mutableCopy];
  }
  return self;
}

@end

NS_ASSUME_NONNULL_END