aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--Example/Core/Tests/FIRAnalyticsConfigurationTest.m100
-rw-r--r--Example/Core/Tests/FIRAppTest.m104
-rw-r--r--Example/Core/Tests/FIRComponentContainerTest.m157
-rw-r--r--Example/Core/Tests/FIRComponentTypeTest.m60
-rw-r--r--Example/Core/Tests/FIRLoggerTest.m22
-rw-r--r--Example/Core/Tests/FIRTestComponents.h63
-rw-r--r--Example/Core/Tests/FIRTestComponents.m118
-rw-r--r--Example/Firebase.xcodeproj/project.pbxproj38
-rw-r--r--Firebase/Auth/CHANGELOG.md6
-rw-r--r--Firebase/Core/FIRApp.m33
-rw-r--r--Firebase/Core/FIRComponent.m65
-rw-r--r--Firebase/Core/FIRComponentContainer.m205
-rw-r--r--Firebase/Core/FIRComponentType.m28
-rw-r--r--Firebase/Core/FIRDependency.m44
-rw-r--r--Firebase/Core/FIRLogger.m22
-rw-r--r--Firebase/Core/Private/FIRAppAssociationRegistration.h1
-rw-r--r--Firebase/Core/Private/FIRAppInternal.h20
-rw-r--r--Firebase/Core/Private/FIRComponent.h91
-rw-r--r--Firebase/Core/Private/FIRComponentContainer.h47
-rw-r--r--Firebase/Core/Private/FIRComponentContainerInternal.h39
-rw-r--r--Firebase/Core/Private/FIRComponentRegistrant.h38
-rw-r--r--Firebase/Core/Private/FIRComponentType.h35
-rw-r--r--Firebase/Core/Private/FIRCoreConfigurable.h38
-rw-r--r--Firebase/Core/Private/FIRDependency.h45
-rw-r--r--cmake/external/grpc.cmake19
-rw-r--r--cmake/external/zlib.cmake41
27 files changed, 1383 insertions, 97 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c2bf56d..84722d8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,6 +52,7 @@ enable_testing()
# does not exist yet.
include(external/FirebaseCore)
include(external/googletest)
+include(external/zlib)
include(external/leveldb)
include(external/protobuf)
include(external/nanopb)
diff --git a/Example/Core/Tests/FIRAnalyticsConfigurationTest.m b/Example/Core/Tests/FIRAnalyticsConfigurationTest.m
index a66ad06..8cf9da4 100644
--- a/Example/Core/Tests/FIRAnalyticsConfigurationTest.m
+++ b/Example/Core/Tests/FIRAnalyticsConfigurationTest.m
@@ -20,19 +20,25 @@
#import <FirebaseCore/FIRAnalyticsConfiguration.h>
@interface FIRAnalyticsConfigurationTest : FIRTestCase
-/// A mock for [NSNotificationCenter defaultCenter].
-@property(nonatomic, strong) id notificationCenterMock;
+/// An observer for NSNotificationCenter.
+@property(nonatomic, strong) id observerMock;
+
+@property(nonatomic, strong) NSNotificationCenter *notificationCenter;
@end
@implementation FIRAnalyticsConfigurationTest
- (void)setUp {
[super setUp];
- _notificationCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]);
+
+ _observerMock = OCMObserverMock();
+ _notificationCenter = [NSNotificationCenter defaultCenter];
}
- (void)tearDown {
- [_notificationCenterMock stopMocking];
+ _observerMock = nil;
+ _notificationCenter = nil;
+
[super tearDown];
}
@@ -44,49 +50,73 @@
/// Test that setting the minimum session interval on the singleton fires a notification.
- (void)testMinimumSessionIntervalNotification {
+ // Pick a value to set as the session interval and verify it's in the userInfo dictionary of the
+ // posted notification.
+ NSNumber *sessionInterval = @2601;
+
+ // Set up the expectation for the notification.
FIRAnalyticsConfiguration *config = [FIRAnalyticsConfiguration sharedInstance];
- [config setMinimumSessionInterval:2601];
NSString *notificationName = kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotification;
- OCMVerify([self.notificationCenterMock postNotificationName:notificationName
- object:config
- userInfo:@{
- notificationName : @2601
- }]);
+ [self expectNotificationForObserver:self.observerMock
+ notificationName:notificationName
+ object:config
+ userInfo:@{notificationName : sessionInterval}];
+
+ // Trigger the notification.
+ [config setMinimumSessionInterval:[sessionInterval integerValue]];
+
+ // Verify the observer mock.
+ OCMVerifyAll(self.observerMock);
}
/// Test that setting the minimum session timeout interval on the singleton fires a notification.
- (void)testSessionTimeoutIntervalNotification {
+ // Pick a value to set as the timeout interval and verify it's in the userInfo dictionary of the
+ // posted notification.
+ NSNumber *timeoutInterval = @1000;
+
+ // Set up the expectation for the notification.
FIRAnalyticsConfiguration *config = [FIRAnalyticsConfiguration sharedInstance];
- [config setSessionTimeoutInterval:1000];
NSString *notificationName = kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification;
- OCMVerify([self.notificationCenterMock postNotificationName:notificationName
- object:config
- userInfo:@{
- notificationName : @1000
- }]);
+ [self expectNotificationForObserver:self.observerMock
+ notificationName:notificationName
+ object:config
+ userInfo:@{notificationName : timeoutInterval}];
+
+ // Trigger the notification.
+ [config setSessionTimeoutInterval:[timeoutInterval integerValue]];
+
+ /// Verify the observer mock.
+ OCMVerifyAll(self.observerMock);
}
- (void)testSettingAnalyticsCollectionEnabled {
- // The ordering matters for these notifications.
- [self.notificationCenterMock setExpectationOrderMatters:YES];
-
- // Test setting to enabled.
+ // Test setting to enabled. The ordering matters for these notifications.
FIRAnalyticsConfiguration *config = [FIRAnalyticsConfiguration sharedInstance];
NSString *notificationName = kFIRAnalyticsConfigurationSetEnabledNotification;
+ [self.notificationCenter addMockObserver:self.observerMock name:notificationName object:config];
+
+ [self.observerMock setExpectationOrderMatters:YES];
+ [[self.observerMock expect] notificationWithName:notificationName
+ object:config
+ userInfo:@{
+ notificationName : @YES
+ }];
+
+ // Test setting to enabled.
[config setAnalyticsCollectionEnabled:YES];
- OCMVerify([self.notificationCenterMock postNotificationName:notificationName
- object:config
- userInfo:@{
- notificationName : @YES
- }]);
+
+ // Expect the second notification.
+ [[self.observerMock expect] notificationWithName:notificationName
+ object:config
+ userInfo:@{
+ notificationName : @NO
+ }];
// Test setting to disabled.
[config setAnalyticsCollectionEnabled:NO];
- OCMVerify([self.notificationCenterMock postNotificationName:notificationName
- object:config
- userInfo:@{
- notificationName : @NO
- }]);
+
+ OCMVerifyAll(self.observerMock);
}
- (void)testSettingAnalyticsCollectionPersistence {
@@ -114,4 +144,14 @@
[userDefaultsMock stopMocking];
}
+#pragma mark - Private Test Helpers
+
+- (void)expectNotificationForObserver:(id)observer
+ notificationName:(NSNotificationName)name
+ object:(nullable id)object
+ userInfo:(nullable NSDictionary *)userInfo {
+ [self.notificationCenter addMockObserver:self.observerMock name:name object:object];
+ [[observer expect] notificationWithName:name object:object userInfo:userInfo];
+}
+
@end
diff --git a/Example/Core/Tests/FIRAppTest.m b/Example/Core/Tests/FIRAppTest.m
index 549c1ab..656f046 100644
--- a/Example/Core/Tests/FIRAppTest.m
+++ b/Example/Core/Tests/FIRAppTest.m
@@ -52,8 +52,9 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
@property(nonatomic) id appClassMock;
@property(nonatomic) id optionsInstanceMock;
-@property(nonatomic) id notificationCenterMock;
+@property(nonatomic) id observerMock;
@property(nonatomic) FIRApp *app;
+@property(nonatomic) NSNotificationCenter *notificationCenter;
@end
@@ -65,13 +66,19 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
[FIRApp resetApps];
_appClassMock = OCMClassMock([FIRApp class]);
_optionsInstanceMock = OCMPartialMock([FIROptions defaultOptions]);
- _notificationCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]);
+ _observerMock = OCMObserverMock();
+
+ // TODO: Remove all usages of defaultCenter in Core, then we can instantiate an instance here to
+ // inject instead of using defaultCenter.
+ _notificationCenter = [NSNotificationCenter defaultCenter];
}
- (void)tearDown {
[_appClassMock stopMocking];
[_optionsInstanceMock stopMocking];
- [_notificationCenterMock stopMocking];
+ [_notificationCenter removeObserver:_observerMock];
+ _observerMock = nil;
+ _notificationCenter = nil;
[super tearDown];
}
@@ -79,11 +86,12 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
- (void)testConfigure {
NSDictionary *expectedUserInfo =
[self expectedUserInfoWithAppName:kFIRDefaultAppName isDefaultApp:YES];
- OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
- object:[FIRApp class]
- userInfo:expectedUserInfo]);
+ [self expectNotificationForObserver:self.observerMock
+ notificationName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo];
XCTAssertNoThrow([FIRApp configure]);
- OCMVerifyAll(self.notificationCenterMock);
+ OCMVerifyAll(self.observerMock);
self.app = [FIRApp defaultApp];
XCTAssertNotNil(self.app);
@@ -108,12 +116,13 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
NSDictionary *expectedUserInfo =
[self expectedUserInfoWithAppName:kFIRDefaultAppName isDefaultApp:YES];
- OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
- object:[FIRApp class]
- userInfo:expectedUserInfo]);
+ [self expectNotificationForObserver:self.observerMock
+ notificationName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo];
// default options
XCTAssertNoThrow([FIRApp configureWithOptions:[FIROptions defaultOptions]]);
- OCMVerifyAll(self.notificationCenterMock);
+ OCMVerifyAll(self.observerMock);
self.app = [FIRApp defaultApp];
XCTAssertNotNil(self.app);
@@ -130,12 +139,13 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
options.APIKey = kCustomizedAPIKey;
NSDictionary *expectedUserInfo =
[self expectedUserInfoWithAppName:kFIRDefaultAppName isDefaultApp:YES];
- OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
- object:[FIRApp class]
- userInfo:expectedUserInfo]);
+ [self expectNotificationForObserver:self.observerMock
+ notificationName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo];
XCTAssertNoThrow([FIRApp configureWithOptions:options]);
- OCMVerifyAll(self.notificationCenterMock);
+ OCMVerifyAll(self.observerMock);
self.app = [FIRApp defaultApp];
XCTAssertNotNil(self.app);
@@ -158,11 +168,12 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
NSDictionary *expectedUserInfo =
[self expectedUserInfoWithAppName:kFIRTestAppName1 isDefaultApp:NO];
- OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
- object:[FIRApp class]
- userInfo:expectedUserInfo]);
+ [self expectNotificationForObserver:self.observerMock
+ notificationName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo];
XCTAssertNoThrow([FIRApp configureWithName:kFIRTestAppName1 options:[FIROptions defaultOptions]]);
- OCMVerifyAll(self.notificationCenterMock);
+ OCMVerifyAll(self.observerMock);
XCTAssertTrue([FIRApp allApps].count == 1);
self.app = [FIRApp appNamed:kFIRTestAppName1];
@@ -179,11 +190,16 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
FIROptions *newOptions = [options copy];
newOptions.deepLinkURLScheme = kDeepLinkURLScheme;
+ // Set up notification center observer for verifying notifications.
+ [self.notificationCenter addMockObserver:self.observerMock
+ name:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]];
+
NSDictionary *expectedUserInfo1 =
[self expectedUserInfoWithAppName:kFIRTestAppName1 isDefaultApp:NO];
- OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
- object:[FIRApp class]
- userInfo:expectedUserInfo1]);
+ [[self.observerMock expect] notificationWithName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo1];
XCTAssertNoThrow([FIRApp configureWithName:kFIRTestAppName1 options:newOptions]);
XCTAssertTrue([FIRApp allApps].count == 1);
self.app = [FIRApp appNamed:kFIRTestAppName1];
@@ -196,11 +212,13 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
NSDictionary *expectedUserInfo2 =
[self expectedUserInfoWithAppName:kFIRTestAppName2 isDefaultApp:NO];
- OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
- object:[FIRApp class]
- userInfo:expectedUserInfo2]);
+ [[self.observerMock expect] notificationWithName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo2];
+
+ [self.observerMock setExpectationOrderMatters:YES];
XCTAssertNoThrow([FIRApp configureWithName:kFIRTestAppName2 options:customizedOptions]);
- OCMVerifyAll(self.notificationCenterMock);
+ OCMVerifyAll(self.observerMock);
XCTAssertTrue([FIRApp allApps].count == 2);
self.app = [FIRApp appNamed:kFIRTestAppName2];
@@ -241,12 +259,15 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
[FIRApp configure];
self.app = [FIRApp defaultApp];
XCTAssertTrue([FIRApp allApps].count == 1);
+ [self expectNotificationForObserver:self.observerMock
+ notificationName:kFIRAppDeleteNotification
+ object:[FIRApp class]
+ userInfo:[OCMArg any]];
[self.app deleteApp:^(BOOL success) {
XCTAssertTrue(success);
}];
- OCMVerify([self.notificationCenterMock postNotificationName:kFIRAppDeleteNotification
- object:[FIRApp class]
- userInfo:[OCMArg any]]);
+
+ OCMVerifyAll(self.observerMock);
XCTAssertTrue(self.app.alreadySentDeleteNotification);
XCTAssertTrue([FIRApp allApps].count == 0);
}
@@ -671,16 +692,27 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
- (void)testGlobalDataCollectionNoDiagnosticsSent {
[FIRApp configure];
+ // Add an observer for the diagnostics notification - both with and without an object to ensure it
+ // catches it either way. Currently no object is sent, but in the future that could change.
+ [self.notificationCenter addMockObserver:self.observerMock
+ name:kFIRAppDiagnosticsNotification
+ object:nil];
+ [self.notificationCenter addMockObserver:self.observerMock
+ name:kFIRAppDiagnosticsNotification
+ object:OCMOCK_ANY];
+
// Stub out reading from user defaults since stubbing out the BOOL has issues. If the data
// collection switch is disabled, the `sendLogs` call should return immediately and not fire a
// notification.
OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
.andReturn(@NO);
- OCMReject([self.notificationCenterMock postNotificationName:kFIRAppDiagnosticsNotification
- object:OCMOCK_ANY
- userInfo:OCMOCK_ANY]);
+
NSError *error = [NSError errorWithDomain:@"com.firebase" code:42 userInfo:nil];
[[FIRApp defaultApp] sendLogsWithServiceName:@"Service" version:@"Version" error:error];
+
+ // The observer mock is strict and will raise an exception when an unexpected notification is
+ // received.
+ OCMVerifyAll(self.observerMock);
}
#pragma mark - Analytics Flag Tests
@@ -767,6 +799,14 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
#pragma mark - private
+- (void)expectNotificationForObserver:(id)observer
+ notificationName:(NSNotificationName)name
+ object:(nullable id)object
+ userInfo:(nullable NSDictionary *)userInfo {
+ [self.notificationCenter addMockObserver:observer name:name object:object];
+ [[observer expect] notificationWithName:name object:object userInfo:userInfo];
+}
+
- (NSDictionary<NSString *, NSObject *> *)expectedUserInfoWithAppName:(NSString *)name
isDefaultApp:(BOOL)isDefaultApp {
return @{
diff --git a/Example/Core/Tests/FIRComponentContainerTest.m b/Example/Core/Tests/FIRComponentContainerTest.m
new file mode 100644
index 0000000..b255973
--- /dev/null
+++ b/Example/Core/Tests/FIRComponentContainerTest.m
@@ -0,0 +1,157 @@
+// 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 "FIRTestCase.h"
+
+#import <FirebaseCore/FIRAppInternal.h>
+#import <FirebaseCore/FIRComponent.h>
+#import <FirebaseCore/FIRComponentContainerInternal.h>
+
+#import "FIRTestComponents.h"
+
+/// Internally exposed methods and properties for testing.
+@interface FIRComponentContainer (TestInternal)
+
+@property(nonatomic, strong) NSMutableDictionary<NSString *, FIRComponentCreationBlock> *components;
+@property(nonatomic, strong) NSMutableDictionary<NSString *, id> *cachedInstances;
+
++ (void)registerAsComponentRegistrant:(Class)klass inSet:(NSMutableSet<Class> *)allRegistrants;
+
+- (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet<Class> *)allRegistrants;
+
+@end
+
+@interface FIRComponentContainerTest : FIRTestCase
+
+@end
+
+@implementation FIRComponentContainerTest
+
+#pragma mark - Registration Tests
+
+- (void)testRegisteringConformingClass {
+ NSMutableSet<Class> *allRegistrants = [NSMutableSet<Class> set];
+ Class testClass = [FIRTestClass class];
+ [FIRComponentContainer registerAsComponentRegistrant:testClass inSet:allRegistrants];
+ XCTAssertTrue([allRegistrants containsObject:testClass]);
+}
+
+- (void)testRegisteringNonConformingClass {
+ NSMutableSet<Class> *allRegistrants = [NSMutableSet<Class> set];
+ XCTAssertThrows(
+ [FIRComponentContainer registerAsComponentRegistrant:[NSString class] inSet:allRegistrants]);
+ XCTAssertTrue(allRegistrants.count == 0);
+}
+
+- (void)testComponentsPopulatedOnInit {
+ FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClass class] ]];
+
+ // Verify that the block is stored.
+ NSString *protocolName = NSStringFromProtocol(@protocol(FIRTestProtocol));
+ FIRComponentCreationBlock creationBlock = container.components[protocolName];
+ OCMExpect(creationBlock);
+}
+
+#pragma mark - Caching Tests
+
+- (void)testInstanceCached {
+ FIRComponentContainer *container =
+ [self containerWithRegistrants:@[ [FIRTestClassCached class] ]];
+
+ // Fetch an instance for `FIRTestProtocolCached`, then fetch it again to assert it's cached.
+ id<FIRTestProtocolCached> instance1 = FIR_COMPONENT(FIRTestProtocolCached, container);
+ XCTAssertNotNil(instance1);
+ id<FIRTestProtocolCached> instance2 = FIR_COMPONENT(FIRTestProtocolCached, container);
+ XCTAssertNotNil(instance2);
+ XCTAssertEqual(instance1, instance2);
+}
+
+- (void)testInstanceNotCached {
+ FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClass class] ]];
+
+ // Retrieve an instance from the container, then fetch it again and ensure it's not the same
+ // instance.
+ id<FIRTestProtocol> instance1 = FIR_COMPONENT(FIRTestProtocol, container);
+ XCTAssertNotNil(instance1);
+ id<FIRTestProtocol> instance2 = FIR_COMPONENT(FIRTestProtocol, container);
+ XCTAssertNotNil(instance2);
+ XCTAssertNotEqual(instance1, instance2);
+}
+
+- (void)testRemoveAllCachedInstances {
+ FIRComponentContainer *container = [self containerWithRegistrants:@[
+ [FIRTestClass class], [FIRTestClassCached class], [FIRTestClassEagerCached class]
+ ]];
+
+ // Retrieve an instance of FIRTestClassCached to ensure it's cached.
+ id<FIRTestProtocolCached> cachedInstance1 = FIR_COMPONENT(FIRTestProtocolCached, container);
+ id<FIRTestProtocolEagerCached> eagerInstance1 =
+ FIR_COMPONENT(FIRTestProtocolEagerCached, container);
+
+ // FIRTestClassEagerCached and FIRTestClassCached instances should be cached at this point.
+ XCTAssertTrue(container.cachedInstances.count == 2);
+
+ // Remove the instances and verify cachedInstances is empty, and that new instances returned from
+ // the container don't match the old ones.
+ [container removeAllCachedInstances];
+ XCTAssertTrue(container.cachedInstances.count == 0);
+
+ id<FIRTestProtocolCached> cachedInstance2 = FIR_COMPONENT(FIRTestProtocolCached, container);
+ XCTAssertNotEqual(cachedInstance1, cachedInstance2);
+ id<FIRTestProtocolEagerCached> eagerInstance2 =
+ FIR_COMPONENT(FIRTestProtocolEagerCached, container);
+ XCTAssertNotEqual(eagerInstance1, eagerInstance2);
+}
+
+#pragma mark - Instantiation Tests
+
+- (void)testEagerInstantiation {
+ // Create a container with `FIRTestClassEagerCached` as a registrant, which provides the
+ // implementation for `FIRTestProtocolEagerCached` and requires eager instantiation as well as
+ // caching so the test can verify it was eagerly instantiated.
+ FIRComponentContainer *container =
+ [self containerWithRegistrants:@[ [FIRTestClassEagerCached class] ]];
+ NSString *protocolName = NSStringFromProtocol(@protocol(FIRTestProtocolEagerCached));
+ XCTAssertNotNil(container.cachedInstances[protocolName]);
+}
+
+#pragma mark - Input Validation Tests
+
+- (void)testProtocolAlreadyRegistered {
+ // Register two classes that provide the same protocol. Only one should be stored, and there
+ // should be a log stating that the protocol has already been registered. Right now there's no
+ // guarantee which one will be registered first since it's an NSSet under the hood, but that could
+ // change in the future.
+ // TODO(wilsonryan): Assert that the log gets called warning that it's already been registered.
+ FIRComponentContainer *container =
+ [self containerWithRegistrants:@[ [FIRTestClass class], [FIRTestClassDuplicate class] ]];
+ XCTAssert(container.components.count == 1);
+}
+
+#pragma mark - Convenience Methods
+
+/// Create a container that has registered the test class.
+- (FIRComponentContainer *)containerWithRegistrants:(NSArray<Class> *)registrants {
+ id appMock = OCMClassMock([FIRApp class]);
+ NSMutableSet<Class> *allRegistrants = [NSMutableSet<Class> set];
+
+ // Initialize the container with the test classes.
+ for (Class c in registrants) {
+ [FIRComponentContainer registerAsComponentRegistrant:c inSet:allRegistrants];
+ }
+
+ return [[FIRComponentContainer alloc] initWithApp:appMock registrants:allRegistrants];
+}
+
+@end
diff --git a/Example/Core/Tests/FIRComponentTypeTest.m b/Example/Core/Tests/FIRComponentTypeTest.m
new file mode 100644
index 0000000..7076fdd
--- /dev/null
+++ b/Example/Core/Tests/FIRComponentTypeTest.m
@@ -0,0 +1,60 @@
+// 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 "FIRTestCase.h"
+
+#import <FirebaseCore/FIRComponentContainerInternal.h>
+#import <FirebaseCore/FIRComponentType.h>
+
+#import "FIRTestComponents.h"
+
+@interface FIRComponentTypeTest : FIRTestCase
+
+@property(nonatomic, strong) id componentContainerMock;
+@end
+
+@implementation FIRComponentTypeTest
+
+- (void)setUp {
+ [super setUp];
+ _componentContainerMock = OCMClassMock([FIRComponentContainer class]);
+}
+
+- (void)tearDown {
+ [super tearDown];
+ [_componentContainerMock stopMocking];
+}
+
+- (void)testForwardsCallToContainer {
+ Protocol *testProtocol = @protocol(FIRTestProtocol);
+ OCMExpect([self.componentContainerMock instanceForProtocol:testProtocol]);
+
+ // Grab an instance from the container, through ComponentType.
+ __unused id<FIRTestProtocol> instance =
+ [FIRComponentType<id<FIRTestProtocol>> instanceForProtocol:@protocol(FIRTestProtocol)
+ inContainer:self.componentContainerMock];
+ OCMVerifyAll(self.componentContainerMock);
+}
+
+- (void)testMacroForwardsCallToContainer {
+ Protocol *testProtocol = @protocol(FIRTestProtocol);
+ OCMExpect([self.componentContainerMock instanceForProtocol:testProtocol]);
+
+ // Grab an instance from the container, through the macro that uses FIRComponentType.
+ __unused id<FIRTestProtocol> instance =
+ FIR_COMPONENT(FIRTestProtocol, self.componentContainerMock);
+
+ OCMVerifyAll(self.componentContainerMock);
+}
+@end
diff --git a/Example/Core/Tests/FIRLoggerTest.m b/Example/Core/Tests/FIRLoggerTest.m
index c1ba37b..b871244 100644
--- a/Example/Core/Tests/FIRLoggerTest.m
+++ b/Example/Core/Tests/FIRLoggerTest.m
@@ -31,6 +31,8 @@ extern const char *kFIRLoggerASLClientFacilityName;
extern void FIRResetLogger(void);
+extern void FIRSetLoggerUserDefaults(NSUserDefaults *defaults);
+
extern aslclient getFIRLoggerClient(void);
extern dispatch_queue_t getFIRClientQueue(void);
@@ -43,7 +45,7 @@ static NSString *const kMessageCode = @"I-COR000001";
@property(nonatomic) NSString *randomLogString;
-@property(nonatomic, strong) id userDefaultsMock;
+@property(nonatomic, strong) NSUserDefaults *defaults;
@end
@@ -53,14 +55,15 @@ static NSString *const kMessageCode = @"I-COR000001";
[super setUp];
FIRResetLogger();
- // Stub NSUserDefaults for tracking the error and warning count.
- _userDefaultsMock = OCMPartialMock([NSUserDefaults standardUserDefaults]);
+ // Stub NSUserDefaults for cleaner testing.
+ _defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.firebase.logger_test"];
+ FIRSetLoggerUserDefaults(_defaults);
}
- (void)tearDown {
[super tearDown];
- [_userDefaultsMock stopMocking];
+ _defaults = nil;
}
// Test some stable variables to make sure they weren't accidently changed.
@@ -92,8 +95,7 @@ static NSString *const kMessageCode = @"I-COR000001";
FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error.");
// Assert.
- NSNumber *debugMode =
- [[NSUserDefaults standardUserDefaults] objectForKey:kFIRPersistedDebugModeKey];
+ NSNumber *debugMode = [self.defaults objectForKey:kFIRPersistedDebugModeKey];
XCTAssertNil(debugMode);
XCTAssertFalse(getFIRLoggerDebugMode());
@@ -111,8 +113,7 @@ static NSString *const kMessageCode = @"I-COR000001";
FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error.");
// Assert.
- NSNumber *debugMode =
- [[NSUserDefaults standardUserDefaults] objectForKey:kFIRPersistedDebugModeKey];
+ NSNumber *debugMode = [self.defaults objectForKey:kFIRPersistedDebugModeKey];
XCTAssertTrue(debugMode.boolValue);
XCTAssertTrue(getFIRLoggerDebugMode());
@@ -123,14 +124,13 @@ static NSString *const kMessageCode = @"I-COR000001";
- (void)testInitializeASLForDebugModeWithUserDefaults {
// Stub.
NSNumber *debugMode = @YES;
- OCMStub([self.userDefaultsMock boolForKey:kFIRPersistedDebugModeKey])
- .andReturn(debugMode.boolValue);
+ [self.defaults setBool:debugMode.boolValue forKey:kFIRPersistedDebugModeKey];
// Test.
FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error.");
// Assert.
- debugMode = [[NSUserDefaults standardUserDefaults] objectForKey:kFIRPersistedDebugModeKey];
+ debugMode = [self.defaults objectForKey:kFIRPersistedDebugModeKey];
XCTAssertTrue(debugMode.boolValue);
XCTAssertTrue(getFIRLoggerDebugMode());
}
diff --git a/Example/Core/Tests/FIRTestComponents.h b/Example/Core/Tests/FIRTestComponents.h
new file mode 100644
index 0000000..63b2075
--- /dev/null
+++ b/Example/Core/Tests/FIRTestComponents.h
@@ -0,0 +1,63 @@
+// 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 <Foundation/Foundation.h>
+
+#import <FirebaseCore/FIRComponent.h>
+#import <FirebaseCore/FIRComponentContainer.h>
+#import <FirebaseCore/FIRComponentRegistrant.h>
+
+@protocol FIRComponentRegistrant;
+
+#pragma mark - Standard Component
+
+/// A test protocol to be used for container testing.
+@protocol FIRTestProtocol
+- (void)doSomething;
+@end
+
+/// A test class that is a component registrant.
+@interface FIRTestClass
+ : NSObject <FIRTestProtocol, FIRComponentRegistrant, FIRComponentLifecycleMaintainer>
+@end
+
+/// A test class that is a component registrant, a duplicate of FIRTestClass.
+@interface FIRTestClassDuplicate
+ : NSObject <FIRTestProtocol, FIRComponentRegistrant, FIRComponentLifecycleMaintainer>
+@end
+
+#pragma mark - Eager Component
+
+/// A test protocol to be used for container testing.
+@protocol FIRTestProtocolEagerCached
+- (void)doSomethingFaster;
+@end
+
+/// A test class that is a component registrant that provides a component requiring eager
+/// instantiation, and is cached for easier validation that it was instantiated.
+@interface FIRTestClassEagerCached
+ : NSObject <FIRTestProtocolEagerCached, FIRComponentRegistrant, FIRComponentLifecycleMaintainer>
+@end
+
+#pragma mark - Cached Component
+
+/// A test protocol to be used for container testing.
+@protocol FIRTestProtocolCached
+@end
+
+/// A test class that is a component registrant that provides a component that requests to be
+/// cached.
+@interface FIRTestClassCached
+ : NSObject <FIRTestProtocolCached, FIRComponentRegistrant, FIRComponentLifecycleMaintainer>
+@end
diff --git a/Example/Core/Tests/FIRTestComponents.m b/Example/Core/Tests/FIRTestComponents.m
new file mode 100644
index 0000000..68346f3
--- /dev/null
+++ b/Example/Core/Tests/FIRTestComponents.m
@@ -0,0 +1,118 @@
+// 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 "FIRTestComponents.h"
+
+#import <FirebaseCore/FIRComponent.h>
+
+#pragma mark - Standard Component
+
+@implementation FIRTestClass
+
+/// FIRTestProtocol conformance.
+- (void)doSomething {
+}
+
+/// FIRComponentRegistrant conformance.
++ (nonnull NSArray<FIRComponent *> *)componentsToRegister {
+ FIRComponent *testComponent =
+ [FIRComponent componentWithProtocol:@protocol(FIRTestProtocol)
+ creationBlock:^id _Nullable(FIRComponentContainer *_Nonnull container,
+ BOOL *_Nonnull isCacheable) {
+ return [[FIRTestClass alloc] init];
+ }];
+ return @[ testComponent ];
+}
+
+/// FIRComponentLifecycleMaintainer conformance.
+- (void)appWillBeDeleted:(FIRApp *)app {
+}
+
+@end
+
+/// A test class that is a component registrant, a duplicate of FIRTestClass.
+@implementation FIRTestClassDuplicate
+
+- (void)doSomething {
+}
+
+/// FIRComponentRegistrant conformance.
++ (nonnull NSArray<FIRComponent *> *)componentsToRegister {
+ FIRComponent *testComponent =
+ [FIRComponent componentWithProtocol:@protocol(FIRTestProtocol)
+ creationBlock:^id _Nullable(FIRComponentContainer *_Nonnull container,
+ BOOL *_Nonnull isCacheable) {
+ return [[FIRTestClassDuplicate alloc] init];
+ }];
+ return @[ testComponent ];
+}
+
+/// FIRComponentLifecycleMaintainer conformance.
+- (void)appWillBeDeleted:(FIRApp *)app {
+}
+
+@end
+
+#pragma mark - Eager Component
+
+@implementation FIRTestClassEagerCached
+
+/// FIRTestProtocolEager conformance.
+- (void)doSomethingFaster {
+}
+
+/// FIRComponentRegistrant conformance.
++ (nonnull NSArray<FIRComponent *> *)componentsToRegister {
+ FIRComponent *testComponent = [FIRComponent
+ componentWithProtocol:@protocol(FIRTestProtocolEagerCached)
+ instantiationTiming:FIRInstantiationTimingAlwaysEager
+ dependencies:@[]
+ creationBlock:^id _Nullable(FIRComponentContainer *_Nonnull container,
+ BOOL *_Nonnull isCacheable) {
+ FIRTestClassEagerCached *instance = [[FIRTestClassEagerCached alloc] init];
+ *isCacheable = YES;
+ [instance doSomethingFaster];
+ return instance;
+ }];
+ return @[ testComponent ];
+}
+
+/// FIRComponentLifecycleMaintainer conformance.
+- (void)appWillBeDeleted:(FIRApp *)app {
+}
+
+@end
+
+#pragma mark - Cached Component
+
+@implementation FIRTestClassCached
+
+/// FIRComponentRegistrant conformance.
++ (nonnull NSArray<FIRComponent *> *)componentsToRegister {
+ FIRComponent *testComponent = [FIRComponent
+ componentWithProtocol:@protocol(FIRTestProtocolCached)
+ creationBlock:^id _Nullable(FIRComponentContainer *_Nonnull container,
+ BOOL *_Nonnull isCacheable) {
+ FIRTestClassCached *instanceToCache = [[FIRTestClassCached alloc] init];
+ *isCacheable = YES;
+ return instanceToCache;
+ }];
+ return @[ testComponent ];
+}
+
+/// FIRComponentLifecycleMaintainer conformance.
+- (void)appWillBeDeleted:(FIRApp *)app {
+}
+
+@end
diff --git a/Example/Firebase.xcodeproj/project.pbxproj b/Example/Firebase.xcodeproj/project.pbxproj
index 61c4c6a..8d3c13b 100644
--- a/Example/Firebase.xcodeproj/project.pbxproj
+++ b/Example/Firebase.xcodeproj/project.pbxproj
@@ -568,6 +568,15 @@
DEF6C33D1FBCE775005D0740 /* FIRVerifyPasswordRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93151F1E86C6FF0083EDBF /* FIRVerifyPasswordRequestTest.m */; };
DEF6C33E1FBCE775005D0740 /* FIRVerifyPasswordResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315201E86C6FF0083EDBF /* FIRVerifyPasswordResponseTests.m */; };
DEF6C3411FBCE775005D0740 /* OCMStubRecorder+FIRAuthUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315241E86C6FF0083EDBF /* OCMStubRecorder+FIRAuthUnitTests.m */; };
+ ED34CF4E20DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */; };
+ ED34CF4F20DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */; };
+ ED34CF5020DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */; };
+ ED34CF5120DC16DD000EA5D1 /* FIRTestComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */; };
+ ED34CF5220DC16DD000EA5D1 /* FIRTestComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */; };
+ ED34CF5320DC16DD000EA5D1 /* FIRTestComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */; };
+ ED34CF5420DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */; };
+ ED34CF5520DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */; };
+ ED34CF5620DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */; };
ED8C81002088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */; };
ED8C81012088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */; };
ED8C81022088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */; };
@@ -1255,6 +1264,10 @@
DEE14D7D1E844677006FA992 /* Tests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
DEF288401F9AB6E100D480CF /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; };
E2C2834C90DBAB56D568189F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
+ ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRComponentContainerTest.m; sourceTree = "<group>"; };
+ ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRTestComponents.m; sourceTree = "<group>"; };
+ ED34CF4C20DC16DD000EA5D1 /* FIRTestComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRTestComponents.h; sourceTree = "<group>"; };
+ ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRComponentTypeTest.m; sourceTree = "<group>"; };
ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRMutableDictionaryTest.m; sourceTree = "<group>"; };
ED8C80FB2088EFA10093EB8A /* FIRNetworkTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRNetworkTest.m; sourceTree = "<group>"; };
ED8C80FC2088EFA20093EB8A /* FIRReachabilityCheckerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRReachabilityCheckerTest.m; sourceTree = "<group>"; };
@@ -2265,21 +2278,25 @@
DEE14D741E844677006FA992 /* Tests */ = {
isa = PBXGroup;
children = (
- ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */,
- ED8C80FB2088EFA10093EB8A /* FIRNetworkTest.m */,
- ED8C80FC2088EFA20093EB8A /* FIRReachabilityCheckerTest.m */,
- ED8C80FD2088EFA20093EB8A /* third_party */,
EDD43AA320BF7C7B005EBB36 /* FIRAnalyticsConfigurationTest.m */,
- DE4B26DE20855F1F0030A38C /* FIRAppEnvironmentUtilTest.m */,
- DEE14D7B1E844677006FA992 /* FIRTestCase.h */,
DEE14D751E844677006FA992 /* FIRAppAssociationRegistrationUnitTests.m */,
+ DE4B26DE20855F1F0030A38C /* FIRAppEnvironmentUtilTest.m */,
DEE14D761E844677006FA992 /* FIRAppTest.m */,
DEE14D771E844677006FA992 /* FIRBundleUtilTest.m */,
+ ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */,
+ ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */,
DEE14D781E844677006FA992 /* FIRConfigurationTest.m */,
DEE14D791E844677006FA992 /* FIRLoggerTest.m */,
+ ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */,
+ ED8C80FB2088EFA10093EB8A /* FIRNetworkTest.m */,
DEE14D7A1E844677006FA992 /* FIROptionsTest.m */,
+ ED8C80FC2088EFA20093EB8A /* FIRReachabilityCheckerTest.m */,
+ DEE14D7B1E844677006FA992 /* FIRTestCase.h */,
DEE14D7C1E844677006FA992 /* FIRTestCase.m */,
+ ED34CF4C20DC16DD000EA5D1 /* FIRTestComponents.h */,
+ ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */,
DEE14D7D1E844677006FA992 /* Tests-Info.plist */,
+ ED8C80FD2088EFA20093EB8A /* third_party */,
);
path = Tests;
sourceTree = "<group>";
@@ -3621,12 +3638,15 @@
D064E6AF1ED9B31C001956DF /* FIRAppAssociationRegistrationUnitTests.m in Sources */,
ED8C81012088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */,
D064E6B01ED9B31C001956DF /* FIRAppTest.m in Sources */,
+ ED34CF5220DC16DD000EA5D1 /* FIRTestComponents.m in Sources */,
D064E6B11ED9B31C001956DF /* FIRConfigurationTest.m in Sources */,
+ ED34CF5520DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */,
DE4B26E120855F500030A38C /* FIRAppEnvironmentUtilTest.m in Sources */,
D064E6B21ED9B31C001956DF /* FIRLoggerTest.m in Sources */,
D064E6B31ED9B31C001956DF /* FIROptionsTest.m in Sources */,
ED8C810A2088EFA20093EB8A /* GTMHTTPServer.m in Sources */,
D064E6B41ED9B31C001956DF /* FIRBundleUtilTest.m in Sources */,
+ ED34CF4F20DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */,
EDD43AA520BF7C7B005EBB36 /* FIRAnalyticsConfigurationTest.m in Sources */,
D064E6B51ED9B31C001956DF /* FIRTestCase.m in Sources */,
);
@@ -4080,12 +4100,15 @@
DEAAD3DA1FBA34250053BF48 /* FIROptionsTest.m in Sources */,
ED8C81022088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */,
DEAAD3D51FBA34250053BF48 /* FIRAppAssociationRegistrationUnitTests.m in Sources */,
+ ED34CF5320DC16DD000EA5D1 /* FIRTestComponents.m in Sources */,
DEAAD3D91FBA34250053BF48 /* FIRLoggerTest.m in Sources */,
+ ED34CF5620DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */,
DE4B26E220855F520030A38C /* FIRAppEnvironmentUtilTest.m in Sources */,
DEAAD3D61FBA34250053BF48 /* FIRAppTest.m in Sources */,
DEAAD3D81FBA34250053BF48 /* FIRConfigurationTest.m in Sources */,
ED8C810B2088EFA20093EB8A /* GTMHTTPServer.m in Sources */,
DEAAD3DB1FBA34250053BF48 /* FIRTestCase.m in Sources */,
+ ED34CF5020DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */,
EDD43AA620BF7C7B005EBB36 /* FIRAnalyticsConfigurationTest.m in Sources */,
DEAAD3D71FBA34250053BF48 /* FIRBundleUtilTest.m in Sources */,
);
@@ -4164,12 +4187,15 @@
DEE14D8E1E84468D006FA992 /* FIRAppAssociationRegistrationUnitTests.m in Sources */,
ED8C81002088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */,
DEE14D8F1E84468D006FA992 /* FIRAppTest.m in Sources */,
+ ED34CF5120DC16DD000EA5D1 /* FIRTestComponents.m in Sources */,
DEE14D911E84468D006FA992 /* FIRConfigurationTest.m in Sources */,
+ ED34CF5420DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */,
DE4B26E020855F4C0030A38C /* FIRAppEnvironmentUtilTest.m in Sources */,
DEE14D921E84468D006FA992 /* FIRLoggerTest.m in Sources */,
DEE14D931E84468D006FA992 /* FIROptionsTest.m in Sources */,
ED8C81092088EFA20093EB8A /* GTMHTTPServer.m in Sources */,
DEE14D901E84468D006FA992 /* FIRBundleUtilTest.m in Sources */,
+ ED34CF4E20DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */,
EDD43AA420BF7C7B005EBB36 /* FIRAnalyticsConfigurationTest.m in Sources */,
DEE14D941E84468D006FA992 /* FIRTestCase.m in Sources */,
);
diff --git a/Firebase/Auth/CHANGELOG.md b/Firebase/Auth/CHANGELOG.md
index 8475c36..2709831 100644
--- a/Firebase/Auth/CHANGELOG.md
+++ b/Firebase/Auth/CHANGELOG.md
@@ -3,9 +3,11 @@
failures during auth operations (#1436).
# v5.0.2
-- Fix an issue where JWT date timestamps weren't parsed correctly.
+- Fix an issue where JWT date timestamps weren't parsed correctly. (#1319)
- Fix an issue where anonymous accounts weren't correctly promoted to
- non-anonymous when linked with passwordless email auth accounts.
+ non-anonymous when linked with passwordless email auth accounts. (#1383)
+- Fix an exception from using an invalidated NSURLSession. (#1261)
+- Fix a data race issue caught by the sanitizer. (#1446)
# v5.0.1
- Restore 4.x level of support for extensions (#1357).
diff --git a/Firebase/Core/FIRApp.m b/Firebase/Core/FIRApp.m
index 91a7aba..2ea7f6b 100644
--- a/Firebase/Core/FIRApp.m
+++ b/Firebase/Core/FIRApp.m
@@ -19,6 +19,8 @@
#import "Private/FIRAnalyticsConfiguration+Internal.h"
#import "Private/FIRAppInternal.h"
#import "Private/FIRBundleUtil.h"
+#import "Private/FIRComponentContainerInternal.h"
+#import "Private/FIRCoreConfigurable.h"
#import "Private/FIRLogger.h"
#import "Private/FIROptionsInternal.h"
#import "third_party/FIRAppEnvironmentUtil.h"
@@ -76,6 +78,12 @@ NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey =
*/
static NSString *const kPlistURL = @"https://console.firebase.google.com/";
+/**
+ * An array of all classes that registered as `FIRCoreConfigurable` in order to receive lifecycle
+ * events from Core.
+ */
+static NSMutableArray<Class<FIRCoreConfigurable>> *gRegisteredAsConfigurable;
+
@interface FIRApp ()
@property(nonatomic) BOOL alreadySentConfigureNotification;
@@ -233,6 +241,10 @@ static NSMutableDictionary *sLibraryVersions;
@synchronized([self class]) {
if (sAllApps && sAllApps[self.name]) {
FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name);
+
+ // Remove all cached instances from the container before deleting the app.
+ [self.container removeAllCachedInstances];
+
[sAllApps removeObjectForKey:self.name];
[self clearDataCollectionSwitchFromUserDefaults];
if ([self.name isEqualToString:kFIRDefaultAppName]) {
@@ -280,6 +292,8 @@ static NSMutableDictionary *sLibraryVersions;
_name = [name copy];
_options = [options copy];
_options.editingLocked = YES;
+ _isDefaultApp = [name isEqualToString:kFIRDefaultAppName];
+ _container = [[FIRComponentContainer alloc] initWithApp:self];
FIRApp *app = sAllApps[name];
_alreadySentConfigureNotification = app.alreadySentConfigureNotification;
@@ -384,6 +398,7 @@ static NSMutableDictionary *sLibraryVersions;
#pragma mark - private
+ (void)sendNotificationsToSDKs:(FIRApp *)app {
+ // TODO: Remove this notification once all SDKs are registered with `FIRCoreConfigurable`.
NSNumber *isDefaultApp = [NSNumber numberWithBool:(app == sDefaultApp)];
NSDictionary *appInfoDict = @{
kFIRAppNameKey : app.name,
@@ -393,6 +408,12 @@ static NSMutableDictionary *sLibraryVersions;
[[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppReadyToConfigureSDKNotification
object:self
userInfo:appInfoDict];
+
+ // This is the new way of sending information to SDKs.
+ // TODO: Do we want this on a background thread, maybe?
+ for (Class<FIRCoreConfigurable> library in gRegisteredAsConfigurable) {
+ [library configureWithApp:app];
+ }
}
+ (NSError *)errorForMissingOptions {
@@ -430,6 +451,18 @@ static NSMutableDictionary *sLibraryVersions;
userInfo:errorDict];
}
++ (void)registerAsConfigurable:(Class<FIRCoreConfigurable>)klass {
+ // This is called at +load time, keep the work to a minimum.
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ gRegisteredAsConfigurable = [[NSMutableArray alloc] initWithCapacity:1];
+ });
+
+ NSAssert([(Class)klass conformsToProtocol:@protocol(FIRCoreConfigurable)],
+ @"The class being registered (%@) must conform to `FIRCoreConfigurable`.", klass);
+ [gRegisteredAsConfigurable addObject:klass];
+}
+
+ (BOOL)isDefaultAppConfigured {
return (sDefaultApp != nil);
}
diff --git a/Firebase/Core/FIRComponent.m b/Firebase/Core/FIRComponent.m
new file mode 100644
index 0000000..2474d1a
--- /dev/null
+++ b/Firebase/Core/FIRComponent.m
@@ -0,0 +1,65 @@
+/*
+ * 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/FIRComponent.h"
+
+#import "Private/FIRComponentContainer.h"
+#import "Private/FIRDependency.h"
+
+@interface FIRComponent ()
+
+- (instancetype)initWithProtocol:(Protocol *)protocol
+ instantiationTiming:(FIRInstantiationTiming)instantiationTiming
+ dependencies:(NSArray<FIRDependency *> *)dependencies
+ creationBlock:(FIRComponentCreationBlock)creationBlock;
+
+@end
+
+@implementation FIRComponent
+
++ (instancetype)componentWithProtocol:(Protocol *)protocol
+ creationBlock:(FIRComponentCreationBlock)creationBlock {
+ return [[FIRComponent alloc] initWithProtocol:protocol
+ instantiationTiming:FIRInstantiationTimingLazy
+ dependencies:@[]
+ creationBlock:creationBlock];
+}
+
++ (instancetype)componentWithProtocol:(Protocol *)protocol
+ instantiationTiming:(FIRInstantiationTiming)instantiationTiming
+ dependencies:(NSArray<FIRDependency *> *)dependencies
+ creationBlock:(FIRComponentCreationBlock)creationBlock {
+ return [[FIRComponent alloc] initWithProtocol:protocol
+ instantiationTiming:instantiationTiming
+ dependencies:dependencies
+ creationBlock:creationBlock];
+}
+
+- (instancetype)initWithProtocol:(Protocol *)protocol
+ instantiationTiming:(FIRInstantiationTiming)instantiationTiming
+ dependencies:(NSArray<FIRDependency *> *)dependencies
+ creationBlock:(FIRComponentCreationBlock)creationBlock {
+ self = [super init];
+ if (self) {
+ _protocol = protocol;
+ _instantiationTiming = instantiationTiming;
+ _dependencies = [dependencies copy];
+ _creationBlock = creationBlock;
+ }
+ return self;
+}
+
+@end
diff --git a/Firebase/Core/FIRComponentContainer.m b/Firebase/Core/FIRComponentContainer.m
new file mode 100644
index 0000000..381c95c
--- /dev/null
+++ b/Firebase/Core/FIRComponentContainer.m
@@ -0,0 +1,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
diff --git a/Firebase/Core/FIRComponentType.m b/Firebase/Core/FIRComponentType.m
new file mode 100644
index 0000000..bdc004f
--- /dev/null
+++ b/Firebase/Core/FIRComponentType.m
@@ -0,0 +1,28 @@
+/*
+ * 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/FIRComponentType.h"
+
+#import "Private/FIRComponentContainerInternal.h"
+
+@implementation FIRComponentType
+
++ (id)instanceForProtocol:(Protocol *)protocol inContainer:(FIRComponentContainer *)container {
+ // Forward the call to the container.
+ return [container instanceForProtocol:protocol];
+}
+
+@end
diff --git a/Firebase/Core/FIRDependency.m b/Firebase/Core/FIRDependency.m
new file mode 100644
index 0000000..f979984
--- /dev/null
+++ b/Firebase/Core/FIRDependency.m
@@ -0,0 +1,44 @@
+/*
+ * 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/FIRDependency.h"
+
+@interface FIRDependency ()
+
+- (instancetype)initWithProtocol:(Protocol *)protocol isRequired:(BOOL)required;
+
+@end
+
+@implementation FIRDependency
+
++ (instancetype)dependencyWithProtocol:(Protocol *)protocol {
+ return [[self alloc] initWithProtocol:protocol isRequired:YES];
+}
+
++ (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required {
+ return [[self alloc] initWithProtocol:protocol isRequired:required];
+}
+
+- (instancetype)initWithProtocol:(Protocol *)protocol isRequired:(BOOL)required {
+ self = [super init];
+ if (self) {
+ _protocol = protocol;
+ _isRequired = required;
+ }
+ return self;
+}
+
+@end
diff --git a/Firebase/Core/FIRLogger.m b/Firebase/Core/FIRLogger.m
index ae14e9f..2784ae9 100644
--- a/Firebase/Core/FIRLogger.m
+++ b/Firebase/Core/FIRLogger.m
@@ -64,6 +64,10 @@ static aslclient sFIRLoggerClient;
static dispatch_queue_t sFIRClientQueue;
+/// NSUserDefaults that should be used to store and read variables. If nil, `standardUserDefaults`
+/// will be used.
+static NSUserDefaults *sFIRLoggerUserDefaults;
+
static BOOL sFIRLoggerDebugMode;
// The sFIRAnalyticsDebugMode flag is here to support the -FIRDebugEnabled/-FIRDebugDisabled
@@ -113,14 +117,17 @@ void FIRLoggerInitializeASL() {
sFIRAnalyticsDebugMode = NO;
sFIRLoggerMaximumLevel = FIRLoggerLevelNotice;
- NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
- BOOL debugMode = [userDefaults boolForKey:kFIRPersistedDebugModeKey];
+ // Use the standard NSUserDefaults if it hasn't been explicitly set.
+ if (sFIRLoggerUserDefaults == nil) {
+ sFIRLoggerUserDefaults = [NSUserDefaults standardUserDefaults];
+ }
+ BOOL debugMode = [sFIRLoggerUserDefaults boolForKey:kFIRPersistedDebugModeKey];
if ([arguments containsObject:kFIRDisableDebugModeApplicationArgument]) { // Default mode
- [userDefaults removeObjectForKey:kFIRPersistedDebugModeKey];
+ [sFIRLoggerUserDefaults removeObjectForKey:kFIRPersistedDebugModeKey];
} else if ([arguments containsObject:kFIREnableDebugModeApplicationArgument] ||
debugMode) { // Debug mode
- [userDefaults setBool:YES forKey:kFIRPersistedDebugModeKey];
+ [sFIRLoggerUserDefaults setBool:YES forKey:kFIRPersistedDebugModeKey];
asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
sFIRLoggerDebugMode = YES;
}
@@ -190,7 +197,12 @@ __attribute__((no_sanitize("thread"))) BOOL FIRIsLoggableLevel(FIRLoggerLevel lo
#ifdef DEBUG
void FIRResetLogger() {
sFIRLoggerOnceToken = 0;
- [[NSUserDefaults standardUserDefaults] removeObjectForKey:kFIRPersistedDebugModeKey];
+ [sFIRLoggerUserDefaults removeObjectForKey:kFIRPersistedDebugModeKey];
+ sFIRLoggerUserDefaults = nil;
+}
+
+void FIRSetLoggerUserDefaults(NSUserDefaults *defaults) {
+ sFIRLoggerUserDefaults = defaults;
}
aslclient getFIRLoggerClient() {
diff --git a/Firebase/Core/Private/FIRAppAssociationRegistration.h b/Firebase/Core/Private/FIRAppAssociationRegistration.h
index a1f8c41..007e2bf 100644
--- a/Firebase/Core/Private/FIRAppAssociationRegistration.h
+++ b/Firebase/Core/Private/FIRAppAssociationRegistration.h
@@ -18,6 +18,7 @@
NS_ASSUME_NONNULL_BEGIN
+// TODO: Remove this once Auth moves over to Core's instance registration system.
/** @class FIRAppAssociationRegistration
@brief Manages object associations as a singleton-dependent: At most one object is
registered for any given host/key pair, and the object shall be created on-the-fly when
diff --git a/Firebase/Core/Private/FIRAppInternal.h b/Firebase/Core/Private/FIRAppInternal.h
index 66979eb..45de2ba 100644
--- a/Firebase/Core/Private/FIRAppInternal.h
+++ b/Firebase/Core/Private/FIRAppInternal.h
@@ -17,6 +17,9 @@
#import "FIRApp.h"
#import "FIRErrors.h"
+@class FIRComponentContainer;
+@protocol FIRCoreConfigurable;
+
/**
* The internal interface to FIRApp. This is meant for first-party integrators, who need to receive
* FIRApp notifications, log info about the success or failure of their configuration, and access
@@ -126,6 +129,11 @@ typedef NSString *_Nullable (^FIRAppGetUIDImplementation)(void);
@interface FIRApp ()
+/**
+ * A flag indicating if this is the default app.
+ */
+@property(nonatomic, readonly) BOOL isDefaultApp;
+
/** @property getTokenImplementation
@brief Gets or sets the block to use for the implementation of
@c getTokenForcingRefresh:withCallback:
@@ -137,6 +145,11 @@ typedef NSString *_Nullable (^FIRAppGetUIDImplementation)(void);
*/
@property(nonatomic, copy) FIRAppGetUIDImplementation getUIDImplementation;
+/*
+ * The container of interop SDKs for this app.
+ */
+@property(nonatomic) FIRComponentContainer *container;
+
/**
* Creates an error for failing to configure a subspec service. This method is called by each
* FIRApp notification listener.
@@ -151,6 +164,13 @@ typedef NSString *_Nullable (^FIRAppGetUIDImplementation)(void);
+ (BOOL)isDefaultAppConfigured;
/**
+ * Register a class that conforms to `FIRCoreConfigurable`. Each SDK should have one class that
+ * registers in order to provide critical information for interoperability and lifecycle events.
+ * TODO(wilsonryan): Write more documentation.
+ */
++ (void)registerAsConfigurable:(Class<FIRCoreConfigurable>)klass;
+
+/**
* Registers a given third-party library with the given version number to be reported for
* analyitcs.
*
diff --git a/Firebase/Core/Private/FIRComponent.h b/Firebase/Core/Private/FIRComponent.h
new file mode 100644
index 0000000..cb51ee7
--- /dev/null
+++ b/Firebase/Core/Private/FIRComponent.h
@@ -0,0 +1,91 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@class FIRApp;
+@class FIRComponentContainer;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Provides a system to clean up cached instances returned from the component system.
+NS_SWIFT_NAME(ComponentLifecycleMaintainer)
+@protocol FIRComponentLifecycleMaintainer
+/// The associated app will be deleted, clean up any resources as they are about to be deallocated.
+- (void)appWillBeDeleted:(FIRApp *)app;
+@end
+
+typedef _Nullable id (^FIRComponentCreationBlock)(FIRComponentContainer *container,
+ BOOL *isCacheable)
+ NS_SWIFT_NAME(ComponentCreationBlock);
+
+@class FIRDependency;
+
+/// Describes the timing of instantiation. Note: new components should default to lazy unless there
+/// is a strong reason to be eager.
+typedef NS_ENUM(NSInteger, FIRInstantiationTiming) {
+ FIRInstantiationTimingLazy,
+ FIRInstantiationTimingAlwaysEager,
+ FIRInstantiationTimingEagerInDefaultApp
+} NS_SWIFT_NAME(InstantiationTiming);
+
+/// A component that can be used from other Firebase SDKs.
+NS_SWIFT_NAME(Component)
+@interface FIRComponent : NSObject
+
+/// The protocol describing functionality provided from the Component.
+@property(nonatomic, strong, readonly) Protocol *protocol;
+
+/// The timing of instantiation.
+@property(nonatomic, readonly) FIRInstantiationTiming instantiationTiming;
+
+/// An array of dependencies for the component.
+@property(nonatomic, copy, readonly) NSArray<FIRDependency *> *dependencies;
+
+/// A block to instantiate an instance of the component with the appropriate dependencies.
+@property(nonatomic, copy, readonly) FIRComponentCreationBlock creationBlock;
+
+// There's an issue with long NS_SWIFT_NAMES that causes compilation to fail, disable clang-format
+// for the next two methods.
+// clang-format off
+
+/// Creates a component with no dependencies that will be lazily initialized.
++ (instancetype)componentWithProtocol:(Protocol *)protocol
+ creationBlock:(FIRComponentCreationBlock)creationBlock
+NS_SWIFT_NAME(init(_:creationBlock:));
+
+/// Creates a component to be registered with the component container.
+///
+/// @param protocol - The protocol describing functionality provided by the component.
+/// @param instantiationTiming - When the component should be initialized. Use .lazy unless there's
+/// a good reason to be instantiated earlier.
+/// @param dependencies - Any dependencies the `implementingClass` has, optional or required.
+/// @param creationBlock - A block to instantiate the component with a container, and if
+/// @return A component that can be registered with the component container.
++ (instancetype)componentWithProtocol:(Protocol *)protocol
+ instantiationTiming:(FIRInstantiationTiming)instantiationTiming
+ dependencies:(NSArray<FIRDependency *> *)dependencies
+ creationBlock:(FIRComponentCreationBlock)creationBlock
+NS_SWIFT_NAME(init(_:instantiationTiming:dependencies:creationBlock:));
+
+// clang-format on
+
+/// Unavailable.
+- (instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/Private/FIRComponentContainer.h b/Firebase/Core/Private/FIRComponentContainer.h
new file mode 100644
index 0000000..10e2255
--- /dev/null
+++ b/Firebase/Core/Private/FIRComponentContainer.h
@@ -0,0 +1,47 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+#import "FIRComponentType.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// A type-safe macro to retrieve a component from a container. This should be used to retrieve
+/// components instead of using the container directly.
+#define FIR_COMPONENT(type, container) \
+ [FIRComponentType<id<type>> instanceForProtocol:@protocol(type) inContainer:container]
+
+@class FIRApp;
+
+/// A container that holds different components that are registered via the
+/// `registerAsComponentRegistrant:` call. These classes should conform to `FIRComponentRegistrant`
+/// in order to properly register components for Core.
+NS_SWIFT_NAME(FirebaseComponentContainer)
+@interface FIRComponentContainer : NSObject
+
+/// A weak reference to the app that an instance of the container belongs to.
+@property(nonatomic, weak, readonly) FIRApp *app;
+
+/// Unavailable. Use the `container` property on `FIRApp`.
+- (instancetype)init NS_UNAVAILABLE;
+
+/// Register a class to provide components for the interoperability system. The class should conform
+/// to `FIRComponentRegistrant` and provide an array of `FIRComponent` objects.
++ (void)registerAsComponentRegistrant:(Class)klass;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/Private/FIRComponentContainerInternal.h b/Firebase/Core/Private/FIRComponentContainerInternal.h
new file mode 100644
index 0000000..bb73e7b
--- /dev/null
+++ b/Firebase/Core/Private/FIRComponentContainerInternal.h
@@ -0,0 +1,39 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+#import "FIRComponent.h"
+#import "FIRComponentContainer.h"
+
+@class FIRApp;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRComponentContainer (Private)
+
+/// Initializes a contain for a given app. This should only be called by the app itself.
+- (instancetype)initWithApp:(FIRApp *)app;
+
+/// Retrieves an instance that conforms to the specified protocol. This will return `nil` if the
+/// protocol wasn't registered, or if the instance couldn't instantiate for the provided app.
+- (nullable id)instanceForProtocol:(Protocol *)protocol NS_SWIFT_NAME(instance(for:));
+
+/// Remove all of the cached instances stored and allow them to clean up after themselves.
+- (void)removeAllCachedInstances;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/Private/FIRComponentRegistrant.h b/Firebase/Core/Private/FIRComponentRegistrant.h
new file mode 100644
index 0000000..ad2cad2
--- /dev/null
+++ b/Firebase/Core/Private/FIRComponentRegistrant.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#ifndef FIRComponentRegistrant_h
+#define FIRComponentRegistrant_h
+
+#import <Foundation/Foundation.h>
+
+@class FIRComponent;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Describes functionality for SDKs registering components in the `FIRComponentContainer`.
+NS_SWIFT_NAME(ComponentRegistrant)
+@protocol FIRComponentRegistrant
+
+/// Returns one or more FIRComponents that will be registered in
+/// FIRApp and participate in dependency resolution and injection.
++ (NSArray<FIRComponent *> *)componentsToRegister;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif /* FIRComponentRegistrant_h */
diff --git a/Firebase/Core/Private/FIRComponentType.h b/Firebase/Core/Private/FIRComponentType.h
new file mode 100644
index 0000000..3254835
--- /dev/null
+++ b/Firebase/Core/Private/FIRComponentType.h
@@ -0,0 +1,35 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@class FIRComponentContainer;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Do not use directly. A placeholder type in order to provide a macro that will warn users of
+/// mis-matched protocols.
+NS_SWIFT_NAME(ComponentType)
+@interface FIRComponentType <__covariant T> : NSObject
+
+/// Do not use directly. A factory method to retrieve an instance that provides a specific
+/// functionality.
++ (T)instanceForProtocol:(Protocol *)protocol
+ inContainer:(FIRComponentContainer *)container;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/Private/FIRCoreConfigurable.h b/Firebase/Core/Private/FIRCoreConfigurable.h
new file mode 100644
index 0000000..6c2b077
--- /dev/null
+++ b/Firebase/Core/Private/FIRCoreConfigurable.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#ifndef FIRCoreConfigurable_h
+#define FIRCoreConfigurable_h
+
+#import <Foundation/Foundation.h>
+
+@class FIRApp;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Provides an interface to set up an SDK once a `FIRApp` is configured.
+NS_SWIFT_NAME(CoreConfigurable)
+@protocol FIRCoreConfigurable
+
+/// Configure the SDK if needed ahead of time. This method is called when the developer calls
+/// `FirebaseApp.configure()`.
++ (void)configureWithApp:(FIRApp *)app;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif /* FIRCoreConfigurable_h */
diff --git a/Firebase/Core/Private/FIRDependency.h b/Firebase/Core/Private/FIRDependency.h
new file mode 100644
index 0000000..46e9b7e
--- /dev/null
+++ b/Firebase/Core/Private/FIRDependency.h
@@ -0,0 +1,45 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// A dependency on a specific protocol's functionality.
+NS_SWIFT_NAME(Dependency)
+@interface FIRDependency : NSObject
+
+/// The protocol describing functionality being depended on.
+@property(nonatomic, strong, readonly) Protocol *protocol;
+
+/// A flag to specify if the dependency is required or not.
+@property(nonatomic, readonly) BOOL isRequired;
+
+/// Initializes a dependency that is required. Calls `initWithProtocol:isRequired` with `YES` for
+/// the required parameter.
+/// Creates a required dependency on the specified protocol's functionality.
++ (instancetype)dependencyWithProtocol:(Protocol *)protocol;
+
+/// Creates a dependency on the specified protocol's functionality and specify if it's required for
+/// the class's functionality.
++ (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required;
+
+/// Use `dependencyWithProtocol:isRequired:` instead.
+- (instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/cmake/external/grpc.cmake b/cmake/external/grpc.cmake
index ee4d246..1e8388c 100644
--- a/cmake/external/grpc.cmake
+++ b/cmake/external/grpc.cmake
@@ -14,7 +14,6 @@
include(ExternalProject)
include(ExternalProjectFlags)
-include(FindZLIB)
if(GRPC_ROOT)
# If the user has supplied a GRPC_ROOT then just use it. Add an empty custom
@@ -69,22 +68,19 @@ else()
## zlib
- # zlib can be built by grpc but we can avoid it on platforms that provide it
- # by default.
- find_package(ZLIB)
+ # cmake/external/zlib.cmake figures out whether or not to build zlib. Either
+ # way, from the gRPC build's point of view it's a package.
+ list(
+ APPEND CMAKE_ARGS
+ -DgRPC_ZLIB_PROVIDER:STRING=package
+ )
if(ZLIB_FOUND)
+ # Propagate possible user configuration to FindZLIB.cmake in the sub-build.
list(
APPEND CMAKE_ARGS
- -DgRPC_ZLIB_PROVIDER:STRING=package
-DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR}
-DZLIB_LIBRARY=${ZLIB_LIBRARY}
)
-
- else()
- list(
- APPEND GIT_SUBMODULES
- third_party/zlib
- )
endif()
@@ -99,6 +95,7 @@ else()
grpc
DEPENDS
protobuf
+ zlib
${GRPC_GIT}
diff --git a/cmake/external/zlib.cmake b/cmake/external/zlib.cmake
new file mode 100644
index 0000000..4744fe9
--- /dev/null
+++ b/cmake/external/zlib.cmake
@@ -0,0 +1,41 @@
+# 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.
+
+include(ExternalProject)
+
+# Use a system- or user-supplied zlib if available
+find_package(ZLIB)
+if(ZLIB_FOUND)
+ add_custom_target(zlib)
+
+else()
+ ExternalProject_Add(
+ zlib
+
+ DOWNLOAD_DIR ${FIREBASE_DOWNLOAD_DIR}
+ DOWNLOAD_NAME zlib-v1.2.11.tar.gz
+ URL https://github.com/madler/zlib/archive/v1.2.11.tar.gz
+ URL_HASH SHA256=629380c90a77b964d896ed37163f5c3a34f6e6d897311f1df2a7016355c45eff
+
+ PREFIX ${PROJECT_BINARY_DIR}/external/zlib
+
+ CMAKE_ARGS
+ -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+ -DCMAKE_INSTALL_PREFIX:STRING=${FIREBASE_INSTALL_DIR}
+ -DBUILD_SHARED_LIBS:BOOL=OFF
+
+ UPDATE_COMMAND ""
+ TEST_COMMAND ""
+ )
+endif()