From 2f6bf8d6695170fe15bb95d2eca6d6b7e56add74 Mon Sep 17 00:00:00 2001 From: Ryan Wilson Date: Thu, 28 Jun 2018 13:48:13 -0400 Subject: 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 --- Example/Core/Tests/FIRComponentContainerTest.m | 157 ++++++++++++++++ Example/Core/Tests/FIRComponentTypeTest.m | 60 ++++++ Example/Core/Tests/FIRTestComponents.h | 63 +++++++ Example/Core/Tests/FIRTestComponents.m | 118 ++++++++++++ Example/Firebase.xcodeproj/project.pbxproj | 38 +++- Firebase/Core/FIRApp.m | 33 ++++ Firebase/Core/FIRComponent.m | 65 +++++++ Firebase/Core/FIRComponentContainer.m | 205 +++++++++++++++++++++ Firebase/Core/FIRComponentType.m | 28 +++ Firebase/Core/FIRDependency.m | 44 +++++ .../Core/Private/FIRAppAssociationRegistration.h | 1 + Firebase/Core/Private/FIRAppInternal.h | 20 ++ Firebase/Core/Private/FIRComponent.h | 91 +++++++++ Firebase/Core/Private/FIRComponentContainer.h | 47 +++++ .../Core/Private/FIRComponentContainerInternal.h | 39 ++++ Firebase/Core/Private/FIRComponentRegistrant.h | 38 ++++ Firebase/Core/Private/FIRComponentType.h | 35 ++++ Firebase/Core/Private/FIRCoreConfigurable.h | 38 ++++ Firebase/Core/Private/FIRDependency.h | 45 +++++ 19 files changed, 1159 insertions(+), 6 deletions(-) create mode 100644 Example/Core/Tests/FIRComponentContainerTest.m create mode 100644 Example/Core/Tests/FIRComponentTypeTest.m create mode 100644 Example/Core/Tests/FIRTestComponents.h create mode 100644 Example/Core/Tests/FIRTestComponents.m create mode 100644 Firebase/Core/FIRComponent.m create mode 100644 Firebase/Core/FIRComponentContainer.m create mode 100644 Firebase/Core/FIRComponentType.m create mode 100644 Firebase/Core/FIRDependency.m create mode 100644 Firebase/Core/Private/FIRComponent.h create mode 100644 Firebase/Core/Private/FIRComponentContainer.h create mode 100644 Firebase/Core/Private/FIRComponentContainerInternal.h create mode 100644 Firebase/Core/Private/FIRComponentRegistrant.h create mode 100644 Firebase/Core/Private/FIRComponentType.h create mode 100644 Firebase/Core/Private/FIRCoreConfigurable.h create mode 100644 Firebase/Core/Private/FIRDependency.h 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 +#import +#import + +#import "FIRTestComponents.h" + +/// Internally exposed methods and properties for testing. +@interface FIRComponentContainer (TestInternal) + +@property(nonatomic, strong) NSMutableDictionary *components; +@property(nonatomic, strong) NSMutableDictionary *cachedInstances; + ++ (void)registerAsComponentRegistrant:(Class)klass inSet:(NSMutableSet *)allRegistrants; + +- (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet *)allRegistrants; + +@end + +@interface FIRComponentContainerTest : FIRTestCase + +@end + +@implementation FIRComponentContainerTest + +#pragma mark - Registration Tests + +- (void)testRegisteringConformingClass { + NSMutableSet *allRegistrants = [NSMutableSet set]; + Class testClass = [FIRTestClass class]; + [FIRComponentContainer registerAsComponentRegistrant:testClass inSet:allRegistrants]; + XCTAssertTrue([allRegistrants containsObject:testClass]); +} + +- (void)testRegisteringNonConformingClass { + NSMutableSet *allRegistrants = [NSMutableSet 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 instance1 = FIR_COMPONENT(FIRTestProtocolCached, container); + XCTAssertNotNil(instance1); + id 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 instance1 = FIR_COMPONENT(FIRTestProtocol, container); + XCTAssertNotNil(instance1); + id 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 cachedInstance1 = FIR_COMPONENT(FIRTestProtocolCached, container); + id 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 cachedInstance2 = FIR_COMPONENT(FIRTestProtocolCached, container); + XCTAssertNotEqual(cachedInstance1, cachedInstance2); + id 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 *)registrants { + id appMock = OCMClassMock([FIRApp class]); + NSMutableSet *allRegistrants = [NSMutableSet 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 +#import + +#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 instance = + [FIRComponentType> 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 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 + +#import +#import +#import + +@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 +@end + +/// A test class that is a component registrant, a duplicate of FIRTestClass. +@interface FIRTestClassDuplicate + : NSObject +@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 +@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 +@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 + +#pragma mark - Standard Component + +@implementation FIRTestClass + +/// FIRTestProtocol conformance. +- (void)doSomething { +} + +/// FIRComponentRegistrant conformance. ++ (nonnull NSArray *)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 *)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 *)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 *)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 = ""; }; DEF288401F9AB6E100D480CF /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; E2C2834C90DBAB56D568189F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRComponentContainerTest.m; sourceTree = ""; }; + ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRTestComponents.m; sourceTree = ""; }; + ED34CF4C20DC16DD000EA5D1 /* FIRTestComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRTestComponents.h; sourceTree = ""; }; + ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRComponentTypeTest.m; sourceTree = ""; }; ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRMutableDictionaryTest.m; sourceTree = ""; }; ED8C80FB2088EFA10093EB8A /* FIRNetworkTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRNetworkTest.m; sourceTree = ""; }; ED8C80FC2088EFA20093EB8A /* FIRReachabilityCheckerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRReachabilityCheckerTest.m; sourceTree = ""; }; @@ -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 = ""; @@ -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> *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 library in gRegisteredAsConfigurable) { + [library configureWithApp:app]; + } } + (NSError *)errorForMissingOptions { @@ -430,6 +451,18 @@ static NSMutableDictionary *sLibraryVersions; userInfo:errorDict]; } ++ (void)registerAsConfigurable:(Class)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 *)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 *)dependencies + creationBlock:(FIRComponentCreationBlock)creationBlock { + return [[FIRComponent alloc] initWithProtocol:protocol + instantiationTiming:instantiationTiming + dependencies:dependencies + creationBlock:creationBlock]; +} + +- (instancetype)initWithProtocol:(Protocol *)protocol + instantiationTiming:(FIRInstantiationTiming)instantiationTiming + dependencies:(NSArray *)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 *components; + +/// Cached instances of components that requested to be cached. +@property(nonatomic, strong) NSMutableDictionary *cachedInstances; + +@end + +@implementation FIRComponentContainer + +// Collection of all classes that register to provide components. +static NSMutableSet *gFIRComponentRegistrants; + +#pragma mark - Public Registration + ++ (void)registerAsComponentRegistrant:(Class)klass { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gFIRComponentRegistrants = [[NSMutableSet alloc] init]; + }); + + [self registerAsComponentRegistrant:klass inSet:gFIRComponentRegistrants]; +} + ++ (void)registerAsComponentRegistrant:(Class)klass inSet:(NSMutableSet *)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 *)allRegistrants { + self = [super init]; + if (self) { + _app = app; + _cachedInstances = [NSMutableDictionary dictionary]; + _components = [NSMutableDictionary dictionary]; + + [self populateComponentsFromRegisteredClasses:allRegistrants forApp:app]; + } + return self; +} + +- (void)populateComponentsFromRegisteredClasses:(NSSet *)classes forApp:(FIRApp *)app { + // Loop through the verified component registrants and populate the components array. + for (Class 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 *)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. @@ -150,6 +163,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)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 + +@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 *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 *)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 + +#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> 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 + +#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 + +@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 *)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 + +@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 + +@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 + +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 -- cgit v1.2.3