aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Ryan Wilson <wilsonryan@google.com>2018-06-28 13:48:13 -0400
committerGravatar GitHub <noreply@github.com>2018-06-28 13:48:13 -0400
commit2f6bf8d6695170fe15bb95d2eca6d6b7e56add74 (patch)
treea9b8f050c53de11d8a2f0fd16e5631987265c704
parent8d399c78bda9529832d6ecd70a6c4c564c62da6d (diff)
Add FirebaseCore component interoperability. (#1437)
* Add FirebaseCore component interoperability. This puts in place the system that will allow SDKs to register with Core and retrieve functionalities provided by other SDKs. * Updated documentation. * Add copywrite, fix log messages. * Explicitly import headers from Private dir
-rw-r--r--Example/Core/Tests/FIRComponentContainerTest.m157
-rw-r--r--Example/Core/Tests/FIRComponentTypeTest.m60
-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/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/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
19 files changed, 1159 insertions, 6 deletions
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/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/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/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