Skip to content

Commit 563854e

Browse files
authored
Copy component container to GoogleUtilitiesComponents (#4113)
* Move component container to GoogleUtilities. This will allow non-Firebase SDKs to take advantage of the component system. Note: Firebase Core will eventually move over to use this, but more investigation is needed about how this can be done with the least amount of churn for other SDKs. * Fix dependency on Logger. * Add CHANGELOG entry. * Fix selector name. * Move ComponentContainer subspec to new pod. * Rename to GoogleUtilitiesComponents. * Style, make public headers. * Update travis with new name. * Add project to if_changed.sh * Review comments. * Enabled static framework, changed homepage. * Fix missing quote.
1 parent 42118c9 commit 563854e

18 files changed

+1231
-4
lines changed

.travis.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,27 @@ jobs:
414414
- travis_retry ./scripts/pod_lib_lint.rb GoogleUtilities.podspec --use-libraries
415415
- travis_retry ./scripts/pod_lib_lint.rb GoogleUtilities.podspec --use-modular-headers
416416

417+
- stage: test
418+
env:
419+
- PROJECT=GoogleUtilitiesComponents METHOD=pod-lib-lint
420+
script:
421+
- travis_retry ./scripts/if_changed.sh ./scripts/pod_lib_lint.rb GoogleUtilitiesComponents.podspec
422+
423+
- stage: test
424+
osx_image: xcode10.1
425+
env:
426+
- PROJECT=GoogleUtilitiesComponents METHOD=pod-lib-lint
427+
script:
428+
- travis_retry ./scripts/if_changed.sh ./scripts/pod_lib_lint.rb GoogleUtilitiesComponents.podspec
429+
430+
- stage: test
431+
if: type = cron
432+
env:
433+
- PROJECT=GoogleUtilitiesComponentsCron METHOD=pod-lib-lint
434+
script:
435+
- travis_retry ./scripts/pod_lib_lint.rb GoogleUtilitiesComponents.podspec --use-libraries
436+
- travis_retry ./scripts/pod_lib_lint.rb GoogleUtilitiesComponents.podspec --use-modular-headers
437+
417438
- stage: test
418439
env:
419440
- PROJECT=Firebase METHOD=pod-lib-lint

GoogleUtilities/Logger/GULLogger.m

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,10 @@ void GULLogBasic(GULLoggerLevel level,
177177
/**
178178
* Generates the logging functions using macros.
179179
*
180-
* Calling GULLogError(kGULLoggerCore, @"I-COR000001", @"Configure %@ failed.", @"blah") shows:
181-
* yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Error> [{service}][I-COR000001] Configure blah failed.
182-
* Calling GULLogDebug(kGULLoggerCore, @"I-COR000001", @"Configure succeed.") shows:
183-
* yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Debug> [{service}][I-COR000001] Configure succeed.
180+
* Calling GULLogError({service}, @"I-XYZ000001", @"Configure %@ failed.", @"blah") shows:
181+
* yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Error> [{service}][I-XYZ000001] Configure blah failed.
182+
* Calling GULLogDebug({service}, @"I-XYZ000001", @"Configure succeed.") shows:
183+
* yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Debug> [{service}][I-XYZ000001] Configure succeed.
184184
*/
185185
#define GUL_LOGGING_FUNCTION(level) \
186186
void GULLog##level(GULLoggerService service, BOOL force, NSString *messageCode, \

GoogleUtilitiesComponents.podspec

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Pod::Spec.new do |s|
2+
s.name = 'GoogleUtilitiesComponents'
3+
s.version = '1.0.0'
4+
s.summary = 'Google Utilities Component Container for Apple platforms.'
5+
6+
s.description = <<-DESC
7+
An internal Google utility that is a dependency injection system for libraries to depend on other
8+
libraries in a type safe and potentially weak manner.
9+
Not intended for direct public usage.
10+
DESC
11+
12+
s.homepage = 'https://developers.google.com/'
13+
s.license = { :type => 'Apache', :file => 'LICENSE' }
14+
s.authors = 'Google, Inc.'
15+
16+
s.source = {
17+
:git => 'https://github.com/firebase/firebase-ios-sdk.git',
18+
:tag => 'UtilitiesComponents-' + s.version.to_s
19+
}
20+
21+
s.ios.deployment_target = '8.0'
22+
s.osx.deployment_target = '10.11'
23+
s.tvos.deployment_target = '10.0'
24+
25+
s.cocoapods_version = '>= 1.4.0'
26+
s.prefix_header_file = false
27+
s.static_framework = true
28+
29+
s.source_files = 'GoogleUtilitiesComponents/Sources/**/*.[mh]'
30+
s.public_header_files = 'GoogleUtilitiesComponents/Sources/Public/*.h', 'GoogleUtilitiesComponents/Sources/Private/*.h'
31+
s.private_header_files = 'GoogleUtilitiesComponents/Sources/Private/*.h'
32+
s.dependency 'GoogleUtilities/Logger'
33+
34+
s.test_spec 'unit' do |unit_tests|
35+
unit_tests.source_files = 'GoogleUtilitiesComponents/Tests/**/*.[mh]'
36+
unit_tests.requires_arc = 'GoogleUtilitiesComponents/Tests/*/*.[mh]'
37+
unit_tests.requires_app_host = true
38+
unit_tests.dependency 'OCMock'
39+
end
40+
end
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2019 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 "Public/GULCCComponent.h"
18+
19+
#import "Public/GULCCComponentContainer.h"
20+
#import "Public/GULCCDependency.h"
21+
22+
@interface GULCCComponent ()
23+
24+
- (instancetype)initWithProtocol:(Protocol *)protocol
25+
instantiationTiming:(GULCCInstantiationTiming)instantiationTiming
26+
dependencies:(NSArray<GULCCDependency *> *)dependencies
27+
creationBlock:(GULCCComponentCreationBlock)creationBlock;
28+
29+
@end
30+
31+
@implementation GULCCComponent
32+
33+
+ (instancetype)componentWithProtocol:(Protocol *)protocol
34+
creationBlock:(GULCCComponentCreationBlock)creationBlock {
35+
return [[GULCCComponent alloc] initWithProtocol:protocol
36+
instantiationTiming:GULCCInstantiationTimingLazy
37+
dependencies:@[]
38+
creationBlock:creationBlock];
39+
}
40+
41+
+ (instancetype)componentWithProtocol:(Protocol *)protocol
42+
instantiationTiming:(GULCCInstantiationTiming)instantiationTiming
43+
dependencies:(NSArray<GULCCDependency *> *)dependencies
44+
creationBlock:(GULCCComponentCreationBlock)creationBlock {
45+
return [[GULCCComponent alloc] initWithProtocol:protocol
46+
instantiationTiming:instantiationTiming
47+
dependencies:dependencies
48+
creationBlock:creationBlock];
49+
}
50+
51+
- (instancetype)initWithProtocol:(Protocol *)protocol
52+
instantiationTiming:(GULCCInstantiationTiming)instantiationTiming
53+
dependencies:(NSArray<GULCCDependency *> *)dependencies
54+
creationBlock:(GULCCComponentCreationBlock)creationBlock {
55+
self = [super init];
56+
if (self) {
57+
_protocol = protocol;
58+
_instantiationTiming = instantiationTiming;
59+
_dependencies = [dependencies copy];
60+
_creationBlock = creationBlock;
61+
}
62+
return self;
63+
}
64+
65+
@end
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/*
2+
* Copyright 2019 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 "Public/GULCCComponentContainer.h"
18+
19+
#import "Public/GULCCComponent.h"
20+
#import "Public/GULCCLibrary.h"
21+
22+
#import <GoogleUtilities/GULLogger.h>
23+
24+
static GULLoggerService kGULComponentContainer = @"[GoogleUtilitiesComponents]";
25+
26+
NS_ASSUME_NONNULL_BEGIN
27+
28+
@interface GULCCComponentContainer ()
29+
30+
/// The dictionary of components that are registered for a particular app. The key is an NSString
31+
/// of the protocol.
32+
@property(nonatomic, strong)
33+
NSMutableDictionary<NSString *, GULCCComponentCreationBlock> *components;
34+
35+
/// Cached instances of components that requested to be cached.
36+
@property(nonatomic, strong) NSMutableDictionary<NSString *, id> *cachedInstances;
37+
38+
/// Protocols of components that have requested to be eagerly instantiated.
39+
@property(nonatomic, strong, nullable) NSMutableArray<Protocol *> *eagerProtocolsToInstantiate;
40+
41+
@end
42+
43+
@implementation GULCCComponentContainer
44+
45+
// Collection of all classes that register to provide components.
46+
static NSMutableSet<Class> *sGULComponentRegistrants;
47+
48+
#pragma mark - Public Registration
49+
50+
+ (void)registerAsComponentRegistrant:(Class<GULCCLibrary>)klass {
51+
static dispatch_once_t onceToken;
52+
dispatch_once(&onceToken, ^{
53+
sGULComponentRegistrants = [[NSMutableSet<Class> alloc] init];
54+
});
55+
56+
[self registerAsComponentRegistrant:klass inSet:sGULComponentRegistrants];
57+
}
58+
59+
+ (void)registerAsComponentRegistrant:(Class<GULCCLibrary>)klass
60+
inSet:(NSMutableSet<Class> *)allRegistrants {
61+
[allRegistrants addObject:klass];
62+
}
63+
64+
#pragma mark - Internal Initialization
65+
66+
- (instancetype)initWithContext:(nullable id)context {
67+
return [self initWithContext:context registrants:sGULComponentRegistrants];
68+
}
69+
70+
- (instancetype)initWithContext:(nullable id)context
71+
registrants:(NSMutableSet<Class> *)allRegistrants {
72+
self = [super init];
73+
if (self) {
74+
_context = context;
75+
_cachedInstances = [NSMutableDictionary<NSString *, id> dictionary];
76+
_components = [NSMutableDictionary<NSString *, GULCCComponentCreationBlock> dictionary];
77+
78+
[self populateComponentsFromRegisteredClasses:allRegistrants withContext:context];
79+
}
80+
return self;
81+
}
82+
83+
- (void)populateComponentsFromRegisteredClasses:(NSSet<Class> *)classes withContext:(id)context {
84+
// Keep track of any components that need to eagerly instantiate after all components are added.
85+
self.eagerProtocolsToInstantiate = [[NSMutableArray alloc] init];
86+
87+
// Loop through the verified component registrants and populate the components array.
88+
for (Class<GULCCLibrary> klass in classes) {
89+
// Loop through all the components being registered and store them as appropriate.
90+
// Classes which do not provide functionality should use a dummy GULCCComponentRegistrant
91+
// protocol.
92+
for (GULCCComponent *component in [klass componentsToRegister]) {
93+
// Check if the component has been registered before, and error out if so.
94+
NSString *protocolName = NSStringFromProtocol(component.protocol);
95+
if (self.components[protocolName]) {
96+
GULLogError(kGULComponentContainer, NO, @"I-COM000001",
97+
@"Attempted to register protocol %@, but it already has an implementation.",
98+
protocolName);
99+
continue;
100+
}
101+
102+
// Store the creation block for later usage.
103+
self.components[protocolName] = component.creationBlock;
104+
105+
// Queue any protocols that should be eagerly instantiated. Don't instantiate them yet
106+
// because they could depend on other components that haven't been added to the components
107+
// array yet.
108+
if (component.instantiationTiming == GULCCInstantiationTimingAlwaysEager) {
109+
[self.eagerProtocolsToInstantiate addObject:component.protocol];
110+
}
111+
}
112+
}
113+
}
114+
115+
#pragma mark - Instance Creation
116+
117+
- (void)instantiateEagerComponents {
118+
// After all components are registered, instantiate the ones that are requesting eager
119+
// instantiation.
120+
@synchronized(self) {
121+
for (Protocol *protocol in self.eagerProtocolsToInstantiate) {
122+
// Get an instance for the protocol, which will instantiate it since it couldn't have been
123+
// cached yet. Ignore the instance coming back since we don't need it.
124+
__unused id unusedInstance = [self instanceForProtocol:protocol];
125+
}
126+
127+
// All eager instantiation is complete, clear the stored property now.
128+
self.eagerProtocolsToInstantiate = nil;
129+
}
130+
}
131+
132+
/// Instantiate an instance of a class that conforms to the specified protocol.
133+
/// This will:
134+
/// - Call the block to create an instance if possible,
135+
/// - Validate that the instance returned conforms to the protocol it claims to,
136+
/// - Cache the instance if the block requests it
137+
///
138+
/// Note that this method assumes the caller already has @sychronized on self.
139+
- (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol
140+
withBlock:(GULCCComponentCreationBlock)creationBlock {
141+
if (!creationBlock) {
142+
return nil;
143+
}
144+
145+
// Create an instance using the creation block.
146+
BOOL shouldCache = NO;
147+
id instance = creationBlock(self, &shouldCache);
148+
if (!instance) {
149+
return nil;
150+
}
151+
152+
// An instance was created, validate that it conforms to the protocol it claims to.
153+
NSString *protocolName = NSStringFromProtocol(protocol);
154+
if (![instance conformsToProtocol:protocol]) {
155+
GULLogError(kGULComponentContainer, NO, @"I-COM000002",
156+
@"An instance conforming to %@ was requested, but the instance provided does not "
157+
@"conform to the protocol",
158+
protocolName);
159+
}
160+
161+
// The instance is ready to be returned, but check if it should be cached first before returning.
162+
if (shouldCache) {
163+
self.cachedInstances[protocolName] = instance;
164+
}
165+
166+
return instance;
167+
}
168+
169+
#pragma mark - Internal Retrieval
170+
171+
- (nullable id)instanceForProtocol:(Protocol *)protocol {
172+
// Check if there is a cached instance, and return it if so.
173+
NSString *protocolName = NSStringFromProtocol(protocol);
174+
175+
id cachedInstance;
176+
@synchronized(self) {
177+
cachedInstance = self.cachedInstances[protocolName];
178+
if (!cachedInstance) {
179+
// Use the creation block to instantiate an instance and return it.
180+
GULCCComponentCreationBlock creationBlock = self.components[protocolName];
181+
cachedInstance = [self instantiateInstanceForProtocol:protocol withBlock:creationBlock];
182+
}
183+
}
184+
return cachedInstance;
185+
}
186+
187+
#pragma mark - Lifecycle
188+
189+
- (void)removeAllCachedInstances {
190+
@synchronized(self) {
191+
// Loop through the cache and notify each instance that is a maintainer to clean up after
192+
// itself.
193+
for (id instance in self.cachedInstances.allValues) {
194+
if ([instance conformsToProtocol:@protocol(GULCCComponentLifecycleMaintainer)] &&
195+
[instance respondsToSelector:@selector(containerWillBeEmptied:)]) {
196+
[instance containerWillBeEmptied:self];
197+
}
198+
}
199+
200+
// Empty the cache.
201+
[self.cachedInstances removeAllObjects];
202+
}
203+
}
204+
205+
@end
206+
207+
NS_ASSUME_NONNULL_END
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2019 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 "Public/GULCCComponentType.h"
18+
19+
#import "Private/GULCCComponentContainerInternal.h"
20+
21+
@implementation GULCCComponentType
22+
23+
+ (id)instanceForProtocol:(Protocol *)protocol inContainer:(GULCCComponentContainer *)container {
24+
// Forward the call to the container.
25+
return [container instanceForProtocol:protocol];
26+
}
27+
28+
@end

0 commit comments

Comments
 (0)