From 3a2aecbe487c69cec502c39dfe6e315f3a3aece8 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Thu, 5 Nov 2015 12:59:48 +0100 Subject: [PATCH 01/12] circular dependencies --- CHANGELOG.md | 12 +++ Dip/Dip.xcodeproj/project.pbxproj | 6 ++ Dip/Dip/Definition.swift | 46 ++++++++- Dip/Dip/Dip.h | 3 +- Dip/Dip/Dip.swift | 119 +++++++++++++++------- Dip/DipTests/ComponentScopeTests.swift | 124 +++++++++++++++++++++++ Dip/DipTests/DipTests.swift | 39 +++---- Dip/DipTests/RuntimeArgumentsTests.swift | 3 +- README.md | 18 +++- 9 files changed, 301 insertions(+), 69 deletions(-) create mode 100644 Dip/DipTests/ComponentScopeTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index e582131..c2bcb01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # CHANGELOG +## Develop + +#### New Features + +* Added support for circular dependencies by adding `ObjectGraph` scope to reuse resolved instances. + [#11](https://github.com/AliSoftware/Dip/pull/11), [@ilyapuchka](https://github.com/ilyapuchka) + +#### Breaking Changes + +* Removed container thread-safety to enable recursion for circular dependencies. +**Access to the container across threads should be handled by clients now**. + ## 3.0.0 * Added support for factories with up to six runtime arguments. diff --git a/Dip/Dip.xcodeproj/project.pbxproj b/Dip/Dip.xcodeproj/project.pbxproj index 831d203..99f7eb7 100644 --- a/Dip/Dip.xcodeproj/project.pbxproj +++ b/Dip/Dip.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 094526B41BEA51540034E72A /* RuntimeArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094526B31BEA51540034E72A /* RuntimeArguments.swift */; }; 094526B61BEA520B0034E72A /* Definition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094526B51BEA520B0034E72A /* Definition.swift */; }; 094526B81BEA536A0034E72A /* RuntimeArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */; }; + 0989323F1BEBC8CD00ACDA2B /* ComponentScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0989323E1BEBC8CD00ACDA2B /* ComponentScopeTests.swift */; }; + 09969C551BEB7C0A00F93C70 /* Dip.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 09969C541BEB7C0A00F93C70 /* Dip.podspec */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -37,6 +39,8 @@ 094526B31BEA51540034E72A /* RuntimeArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = RuntimeArguments.swift; sourceTree = ""; tabWidth = 2; }; 094526B51BEA520B0034E72A /* Definition.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Definition.swift; sourceTree = ""; tabWidth = 2; }; 094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeArgumentsTests.swift; sourceTree = ""; }; + 0989323E1BEBC8CD00ACDA2B /* ComponentScopeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComponentScopeTests.swift; sourceTree = ""; }; + 09969C541BEB7C0A00F93C70 /* Dip.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Dip.podspec; path = ../Dip.podspec; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -95,6 +99,7 @@ children = ( 094526A01BEA1CFF0034E72A /* DipTests.swift */, 094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */, + 0989323E1BEBC8CD00ACDA2B /* ComponentScopeTests.swift */, 094526A21BEA1CFF0034E72A /* Info.plist */, ); path = DipTests; @@ -219,6 +224,7 @@ buildActionMask = 2147483647; files = ( 094526A11BEA1CFF0034E72A /* DipTests.swift in Sources */, + 0989323F1BEBC8CD00ACDA2B /* ComponentScopeTests.swift in Sources */, 094526B81BEA536A0034E72A /* RuntimeArgumentsTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Dip/Dip/Definition.swift b/Dip/Dip/Definition.swift index dad63b7..d607bc2 100644 --- a/Dip/Dip/Definition.swift +++ b/Dip/Dip/Definition.swift @@ -50,20 +50,62 @@ func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool { public enum ComponentScope { /// Indicates that a new instance of the component will be created each time it's resolved. case Prototype + /// Indicates that instances will be reused during resolve but will be discurded when topmost `resolve` method returns. + case ObjectGraph /// Indicates that resolved component should be retained by container and always reused. case Singleton } ///Definition of type T describes how instances of this type should be created when they are resolved by container. public final class DefinitionOf: Definition { + + /** + Changes scope of the component. + + - parameter scope: new scope value. New definitions have `Prototype` scope + */ + public func inScope(scope: ComponentScope) -> DefinitionOf { + self.scope = scope + return self + } + + /** + Sets the block that will be used to resolve dependencies of the component. + This block will be called before `resolve` returns. + + - parameter block: block to use to resolve dependencies + + - note: + If you have circular dependencies at least one of them should use this block + to resolve it's dependencies. Otherwise code enter infinite loop. + + **Example** + + ```swift + container.register { [unowned container] ClientImp(service: container.resolve() as Service) as Client } + + container.register { ServiceImp() as Service } + .resolveDependencies { container, service in + service.delegate = container.resolve() as Client + } + ``` + + */ + public func resolveDependencies(block: (DependencyContainer, T) -> ()) -> DefinitionOf { + self.resolveDependenciesBlock = block + return self + } + let factory: Any - let scope: ComponentScope + var scope: ComponentScope + var resolveDependenciesBlock: ((DependencyContainer, T) -> ())? - init(factory: Any, scope: ComponentScope = .Prototype) { + init(factory: Any, scope: ComponentScope) { self.factory = factory self.scope = scope } + ///Will be stored only if scope is `Singleton` var resolvedInstance: T? { get { guard scope == .Singleton else { return nil } diff --git a/Dip/Dip/Dip.h b/Dip/Dip/Dip.h index 107545a..5e4265c 100644 --- a/Dip/Dip/Dip.h +++ b/Dip/Dip/Dip.h @@ -2,8 +2,7 @@ // Dip.h // Dip // -// Created by Ilya Puchka on 04.11.15. -// Copyright © 2015 AliSoftware. All rights reserved. +// This code is under MIT Licence. See the LICENCE file for more info. // #import diff --git a/Dip/Dip/Dip.swift b/Dip/Dip/Dip.swift index 62c4ace..33fcd9a 100644 --- a/Dip/Dip/Dip.swift +++ b/Dip/Dip/Dip.swift @@ -27,9 +27,9 @@ import Foundation // MARK: - DependencyContainer /** - * _Dip_'s Dependency Containers allow you to do very simple **Dependency Injection** - * by associating `protocols` to concrete implementations - */ +_Dip_'s Dependency Containers allow you to do very simple **Dependency Injection** +by associating `protocols` to concrete implementations +*/ public class DependencyContainer { /** @@ -42,8 +42,7 @@ public class DependencyContainer { case Int(IntegerLiteralType) } - private var dependencies = [DefinitionKey : Definition]() - private var lock: OSSpinLock = OS_SPINLOCK_INIT + var definitions = [DefinitionKey : Definition]() /** Designated initializer for a DependencyContainer @@ -66,9 +65,7 @@ public class DependencyContainer { Clear all the previously registered dependencies on this container. */ public func reset() { - lockAndDo { - dependencies.removeAll() - } + definitions.removeAll() } // MARK: Register dependencies @@ -79,7 +76,14 @@ public class DependencyContainer { - parameter tag: The arbitrary tag to associate this factory with when registering with that protocol. Pass `nil` to associate with any tag. Default value is `nil`. - parameter factory: The factory to register, with return type of protocol you want to register it for - - note: You must cast the factory return type to the protocol you want to register it with (e.g `MyClass() as MyAPI`) + - note: You must cast the factory return type to the protocol you want to register it for. + Inside factory block if you need to reference container use it as `unowned` to avoid retain cycle. + + **Example** + ```swift + container.register { ServiceImp() as Service } + container.register { [unowned container] ClientImp(service: container.resolve()) as Client } + ``` */ public func register(tag tag: Tag? = nil, factory: ()->T) -> DefinitionOf { return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf @@ -88,11 +92,13 @@ public class DependencyContainer { /** Register a Singleton instance associated with optional tag. - - parameter tag: The arbitrary tag to associate this instance with when registering with that protocol. `nil` to associate with any tag. + - parameter tag: The arbitrary tag to associate this instance with when registering with that protocol. + Pass `nil` to associate with any tag. - parameter instance: The instance to register, with return type of protocol you want to register it for - note: You must cast the instance to the protocol you want to register it with (e.g `MyClass() as MyAPI`) */ + @available(*, deprecated, message="Use inScope(:) method of DefinitionOf instead to define scope.") public func register(tag tag: Tag? = nil, @autoclosure(escaping) instance factory: ()->T) -> DefinitionOf { return register(tag: tag, factory: { factory() }, scope: .Singleton) } @@ -120,9 +126,7 @@ public class DependencyContainer { public func register(tag tag: Tag? = nil, factory: F, scope: ComponentScope) -> DefinitionOf { let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) let definition = DefinitionOf(factory: factory, scope: scope) - lockAndDo { - dependencies[key] = definition - } + definitions[key] = definition return definition } @@ -131,9 +135,11 @@ public class DependencyContainer { /** Resolve a dependency. - If no instance/factory was registered with this `tag` for this `protocol`, it will try to resolve the instance/factory associated with `nil` (no tag). + If no definition was registered with this `tag` for this `protocol`, + it will try to resolve the definition associated with `nil` (no tag). - parameter tag: The arbitrary tag to look for when resolving this protocol. + */ public func resolve(tag tag: Tag? = nil) -> T { return resolve(tag: tag) { (factory: ()->T) in factory() } @@ -161,37 +167,74 @@ public class DependencyContainer { public func resolve(tag tag: Tag? = nil, builder: F->T) -> T { let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) let nilTagKey = tag.map { _ in DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: nil) } - - var resolved: T! - lockAndDo { [unowned self] in - resolved = self._resolve(key, nilTagKey: nilTagKey, builder: builder) + + guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf else { + fatalError("No definition registered with \(key) or \(nilTagKey)." + + "Check the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()`.") } - return resolved + + let usingKey: DefinitionKey? = definition.scope == .ObjectGraph ? key : nil + return _resolve(usingKey, definition: definition, builder: builder) } /// Actually resolve dependency - private func _resolve(key: DefinitionKey, nilTagKey: DefinitionKey?, builder: F->T) -> T { - guard let definition = (self.dependencies[key] ?? self.dependencies[nilTagKey]) as? DefinitionOf else { - fatalError("No instance factory registered with \(key) or \(nilTagKey)") - } + private func _resolve(key: DefinitionKey?, definition: DefinitionOf, builder: F->T) -> T { - if let resolvedInstance = definition.resolvedInstance { - return resolvedInstance + resolvedInstances.incrementDepth() + defer { resolvedInstances.decrementDepth() } + + if let previouslyResolved: T = resolvedInstances.previouslyResolved(key, definition: definition) { + return previouslyResolved } else { - let resolved = builder(definition.factory as! F) - definition.resolvedInstance = resolved - return resolved + let resolvedInstance = builder(definition.factory as! F) + + //when builder calls factory it will in turn resolve sub-dependencies (if there are any) + //when it returns instance that we try to resolve here can be already resolved + //so we return it, throwing away instance created by previous call to builder + if let previouslyResolved: T = resolvedInstances.previouslyResolved(key, definition: definition) { + return previouslyResolved + } + + resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key, definition: definition) + definition.resolveDependenciesBlock?(self, resolvedInstance) + + return resolvedInstance } } // MARK: - Private - private func lockAndDo(@noescape block: Void->Void) { - OSSpinLockLock(&lock) - defer { OSSpinLockUnlock(&lock) } - block() + let resolvedInstances = ResolvedInstances() + + ///Pool to hold instances, created during call to `resolve()`. + ///Before `resolve()` returns pool is drained. + class ResolvedInstances { + var resolvedInstances = [DefinitionKey: Any]() + + func storeResolvedInstance(instance: T, forKey key: DefinitionKey?, definition: DefinitionOf) { + self.resolvedInstances[key] = instance + definition.resolvedInstance = instance + } + + func previouslyResolved(key: DefinitionKey?, definition: DefinitionOf) -> T? { + return (definition.resolvedInstance ?? self.resolvedInstances[key]) as? T + } + + var depth: Int = 0 + + func incrementDepth() { + depth++ + } + + func decrementDepth() { + guard depth-- > 0 else { fatalError("Depth can not be lower than zero") } + if depth == 0 { + resolvedInstances.removeAll() + } + } } + } extension DependencyContainer.Tag: IntegerLiteralConvertible { @@ -229,8 +272,14 @@ public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bo } extension Dictionary { - subscript(key: Key?) -> Value! { - guard let key = key else { return nil } - return self[key] + subscript(key: Key?) -> Value? { + get { + guard let key = key else { return nil } + return self[key] + } + set { + guard let key = key else { return } + self[key] = newValue + } } } diff --git a/Dip/DipTests/ComponentScopeTests.swift b/Dip/DipTests/ComponentScopeTests.swift new file mode 100644 index 0000000..4ac3e8f --- /dev/null +++ b/Dip/DipTests/ComponentScopeTests.swift @@ -0,0 +1,124 @@ +// +// RuntimeArgumentsTests.swift +// DipTests +// +// This code is under MIT Licence. See the LICENCE file for more info. +// + +import XCTest +@testable import Dip + +class ComponentScopeTests: XCTestCase { + + let container = DependencyContainer() + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + container.reset() + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testThatPrototypeIsDefaultScope() { + let def = container.register { ServiceImp1() as Service } + XCTAssertEqual(def.scope, ComponentScope.Prototype) + } + + func testThatCallingInScopeChangesScope() { + let def = container.register { ServiceImp1() as Service }.inScope(.Singleton) + XCTAssertEqual(def.scope, ComponentScope.Singleton) + } + + func testThatItResolvesTypeAsNewInstanceForPrototypeScope() { + //given + container.register { ServiceImp1() as Service } + + //when + let service1 = container.resolve() as Service + let service2 = container.resolve() as Service + + //then + XCTAssertFalse((service1 as! ServiceImp1) === (service2 as! ServiceImp1)) + } + + func testThatItReusesInstanceForSingletonScope() { + //given + container.register { ServiceImp1() as Service }.inScope(.Singleton) + + //when + let service1 = container.resolve() as Service + let service2 = container.resolve() as Service + + //then + XCTAssertTrue((service1 as! ServiceImp1) === (service2 as! ServiceImp1)) + } + + class Server { + weak var client: Client? + + init() {} + } + + class Client { + var server: Server + + init(server: Server) { + self.server = server + } + } + + func testThatItReusesInstanceInObjectGraphScopeDuringResolve() { + //given + container.register { [unowned container] in Client(server: container.resolve()) as Client }.inScope(.ObjectGraph) + container.register { Server() as Server }.resolveDependencies { container, server in + server.client = container.resolve() as Client + }.inScope(.ObjectGraph) + + //when + let client = container.resolve() as Client + + //then + let server = client.server + XCTAssertTrue(server.client === client) + } + + func testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve() { + //given + container.register { [unowned container] in Client(server: container.resolve()) as Client }.inScope(.ObjectGraph) + container.register { Server() as Server }.resolveDependencies { container, server in + server.client = container.resolve() as Client + }.inScope(.ObjectGraph) + + //when + let client = container.resolve() as Client + let server = client.server + + let anotherClient = container.resolve() as Client + let anotherServer = anotherClient.server + + //then + XCTAssertFalse(server === anotherServer) + XCTAssertFalse(client === anotherClient) + } + + func testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag() { + //given + var service2: Service? + container.register() { ServiceImp1() as Service }.inScope(.ObjectGraph).resolveDependencies { (c, _) in + service2 = c.resolve(tag: "service") as Service + } + container.register(tag: "service") { ServiceImp2() as Service}.inScope(.ObjectGraph) + + //when + let service1 = container.resolve(tag: "tag") as Service + + //then + XCTAssertTrue(service1 is ServiceImp1) + XCTAssertTrue(service2 is ServiceImp2) + } + +} diff --git a/Dip/DipTests/DipTests.swift b/Dip/DipTests/DipTests.swift index 4df8d14..fbdb54b 100644 --- a/Dip/DipTests/DipTests.swift +++ b/Dip/DipTests/DipTests.swift @@ -2,8 +2,7 @@ // DipTests.swift // DipTests // -// Created by Ilya Puchka on 04.11.15. -// Copyright © 2015 AliSoftware. All rights reserved. +// This code is under MIT Licence. See the LICENCE file for more info. // import XCTest @@ -13,17 +12,15 @@ protocol Service { func getServiceName() -> String } -class ServiceImp1: Service { +extension Service { func getServiceName() -> String { - return "ServiceImp1" + return "\(self.dynamicType)" } } -class ServiceImp2: Service { - func getServiceName() -> String { - return "ServiceImp2" - } -} +class ServiceImp1: Service {} + +class ServiceImp2: Service {} class DipTests: XCTestCase { @@ -84,28 +81,18 @@ class DipTests: XCTestCase { XCTAssertTrue(service2 is ServiceImp2) } - func testThatItResolvesTypeAsNewInstanceEveryTime() { - //given - container.register { ServiceImp1() as Service } - - //when - let service1 = container.resolve() as Service - let service2 = container.resolve() as Service - - //then - XCTAssertFalse((service1 as! ServiceImp1) === (service2 as! ServiceImp1)) - } - - func testThatItReusesInstanceRegisteredAsSingleton() { + func testThatItCallsResolveDependenciesOnDefinition() { //given - container.register(instance: ServiceImp1() as Service) + var resolveDependenciesCalled = false + container.register { ServiceImp1() as Service }.resolveDependencies { (c, s) in + resolveDependenciesCalled = true + } //when - let service1 = container.resolve() as Service - let service2 = container.resolve() as Service + container.resolve() as Service //then - XCTAssertTrue((service1 as! ServiceImp1) === (service2 as! ServiceImp1)) + XCTAssertTrue(resolveDependenciesCalled) } } diff --git a/Dip/DipTests/RuntimeArgumentsTests.swift b/Dip/DipTests/RuntimeArgumentsTests.swift index d22d447..75c12e0 100644 --- a/Dip/DipTests/RuntimeArgumentsTests.swift +++ b/Dip/DipTests/RuntimeArgumentsTests.swift @@ -2,8 +2,7 @@ // RuntimeArgumentsTests.swift // DipTests // -// Created by Ilya Puchka on 04.11.15. -// Copyright © 2015 AliSoftware. All rights reserved. +// This code is under MIT Licence. See the LICENCE file for more info. // import XCTest diff --git a/README.md b/README.md index 12d47a6..0d56ed1 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,20 @@ func resolve(tag tag: Tag? = nil, _ ``` +### Circular dependencies + +_Dip_ supports circular dependencies. To resolve them use new `ObjectGraph` scope and `resolveDependencies` method of `DefinitionOf` returned by `register` method. + +```swift +container.register { [unowned container] ClientImp(server: container.resolve() as Server) as Client }.inScope(.ObjectGraph) + +container.register { ServerImp() as Server } +.inScope(.ObjectGraph) +.resolveDependencies { container, server in + server.client = container.resolve() as Client +} +``` + ### Concrete Example Somewhere in your App target, register the dependencies: @@ -157,8 +171,8 @@ let dip: DependencyContainer = { let env = ProductionEnvironment(analytics: true) dip.register(instance: env as EnvironmentType) dip.register(instance: WebService() as WebServiceType) - dip.register() { name: String in DummyFriendsProvider(user: name) as FriendsProviderType } - dip.register(tag: "me") { _: String in PlistFriendsProvider(plist: "myfriends") as FriendsProviderType } + dip.register() { (name: String) in DummyFriendsProvider(user: name) as FriendsProviderType } + dip.register(tag: "me") { (_: String) in PlistFriendsProvider(plist: "myfriends") as FriendsProviderType } return dip } ``` From 58efc586db33f55b82126e84aa458311b2e51be4 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Fri, 20 Nov 2015 13:39:23 +0100 Subject: [PATCH 02/12] added scoped function to increment/decrement recursion depth --- Dip/Dip/Dip.swift | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Dip/Dip/Dip.swift b/Dip/Dip/Dip.swift index 33fcd9a..a322274 100644 --- a/Dip/Dip/Dip.swift +++ b/Dip/Dip/Dip.swift @@ -180,26 +180,27 @@ public class DependencyContainer { /// Actually resolve dependency private func _resolve(key: DefinitionKey?, definition: DefinitionOf, builder: F->T) -> T { - resolvedInstances.incrementDepth() - defer { resolvedInstances.decrementDepth() } - - if let previouslyResolved: T = resolvedInstances.previouslyResolved(key, definition: definition) { - return previouslyResolved - } - else { - let resolvedInstance = builder(definition.factory as! F) + return resolvedInstances.resolve { - //when builder calls factory it will in turn resolve sub-dependencies (if there are any) - //when it returns instance that we try to resolve here can be already resolved - //so we return it, throwing away instance created by previous call to builder if let previouslyResolved: T = resolvedInstances.previouslyResolved(key, definition: definition) { return previouslyResolved } + else { + let resolvedInstance = builder(definition.factory) + + //when builder calls factory it will in turn resolve sub-dependencies (if there are any) + //when it returns instance that we try to resolve here can be already resolved + //so we return it, throwing away instance created by previous call to builder + if let previouslyResolved: T = resolvedInstances.previouslyResolved(key, definition: definition) { + return previouslyResolved + } + + resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key) + definition.resolveDependenciesBlock?(self, resolvedInstance) + + return resolvedInstance + } - resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key, definition: definition) - definition.resolveDependenciesBlock?(self, resolvedInstance) - - return resolvedInstance } } @@ -221,17 +222,16 @@ public class DependencyContainer { return (definition.resolvedInstance ?? self.resolvedInstances[key]) as? T } - var depth: Int = 0 - - func incrementDepth() { - depth++ - } + private var depth: Int = 0 - func decrementDepth() { - guard depth-- > 0 else { fatalError("Depth can not be lower than zero") } + func resolve(@noescape block: ()->T) -> T { + depth++ + let resolved = block() + depth-- if depth == 0 { resolvedInstances.removeAll() } + return resolved } } From 9d619d6a25b35c87e465dee91cbb6da43a84f282 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Fri, 20 Nov 2015 14:17:53 +0100 Subject: [PATCH 03/12] added note on thread safety in readme --- CHANGELOG.md | 4 ++-- README.md | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2bcb01..f21a8f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ #### Breaking Changes -* Removed container thread-safety to enable recursion for circular dependencies. -**Access to the container across threads should be handled by clients now**. +* Removed container thread-safety to enable recursion calls to `resolve`. + **Access to container from multiple threads should be handled by clients** from now on. ## 3.0.0 diff --git a/README.md b/README.md index 0d56ed1..094a536 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,11 @@ container.register { ServerImp() as Server } } ``` +### Thread safety + +_Dip_ does not provide thread safety, so you need to make sure you always call `resolve` method of `DependencyContainer` from the single thread. +Otherwise if two threads try to resolve the same type they can get different instances where the same instance is expected. + ### Concrete Example Somewhere in your App target, register the dependencies: From 938108f2abc7bc61945864bbef34a0b154514297 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Fri, 20 Nov 2015 14:39:57 +0100 Subject: [PATCH 04/12] converted DefinitionOf to struct --- Dip/Dip/Definition.swift | 39 +++++++-------- Dip/Dip/Dip.swift | 60 +++++++++++++++++------- Dip/Dip/RuntimeArguments.swift | 24 +++++----- Dip/DipTests/ComponentScopeTests.swift | 24 ++++++---- Dip/DipTests/DipTests.swift | 3 +- Dip/DipTests/RuntimeArgumentsTests.swift | 8 ++-- 6 files changed, 93 insertions(+), 65 deletions(-) diff --git a/Dip/Dip/Definition.swift b/Dip/Dip/Definition.swift index d607bc2..bc5ff47 100644 --- a/Dip/Dip/Definition.swift +++ b/Dip/Dip/Definition.swift @@ -57,17 +57,7 @@ public enum ComponentScope { } ///Definition of type T describes how instances of this type should be created when they are resolved by container. -public final class DefinitionOf: Definition { - - /** - Changes scope of the component. - - - parameter scope: new scope value. New definitions have `Prototype` scope - */ - public func inScope(scope: ComponentScope) -> DefinitionOf { - self.scope = scope - return self - } +public struct DefinitionOf: Definition { /** Sets the block that will be used to resolve dependencies of the component. @@ -84,23 +74,26 @@ public final class DefinitionOf: Definition { ```swift container.register { [unowned container] ClientImp(service: container.resolve() as Service) as Client } - container.register { ServiceImp() as Service } - .resolveDependencies { container, service in + var definition = container.register { ServiceImp() as Service } + definition.resolveDependencies { container, service in service.delegate = container.resolve() as Client } ``` */ - public func resolveDependencies(block: (DependencyContainer, T) -> ()) -> DefinitionOf { - self.resolveDependenciesBlock = block - return self + public mutating func resolveDependencies(container: DependencyContainer, tag: DependencyContainer.Tag? = nil, block: (DependencyContainer, T) -> ()) { + guard resolveDependenciesBlock == nil else { + fatalError("You can not change resolveDependencies block after it was set.") + } + resolveDependenciesBlock = block + container.register(tag: tag, definition: self) } - let factory: Any + let factory: F var scope: ComponentScope var resolveDependenciesBlock: ((DependencyContainer, T) -> ())? - init(factory: Any, scope: ComponentScope) { + init(factory: F, scope: ComponentScope) { self.factory = factory self.scope = scope } @@ -111,10 +104,12 @@ public final class DefinitionOf: Definition { guard scope == .Singleton else { return nil } return _resolvedInstance } - set { - guard scope == .Singleton else { return } - _resolvedInstance = newValue - } + } + + mutating func resolvedInstance(container: DependencyContainer, tag: DependencyContainer.Tag? = nil, instance: T) { + guard scope == .Singleton else { return } + _resolvedInstance = instance + container.register(tag: tag, definition: self) } private var _resolvedInstance: T? diff --git a/Dip/Dip/Dip.swift b/Dip/Dip/Dip.swift index a322274..0c96f47 100644 --- a/Dip/Dip/Dip.swift +++ b/Dip/Dip/Dip.swift @@ -68,13 +68,38 @@ public class DependencyContainer { definitions.removeAll() } + /** + Removes previously registered definition from container. + + - parameter tag: tag used to register definition + - parameter definition: definition to remove + */ + public func remove(tag tag: Tag? = nil, definition: DefinitionOf) { + let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) + definitions[key] = nil + } + + /** + Registers new definiton in container and associate it with provided tag. + Will override already registered definition for the same type and factory associated with the same tag. + + - parameter tag: The arbitrary tag to associate definition with + - parameter definition: definition to register in container + */ + public func register(tag tag: Tag? = nil, definition: DefinitionOf) { + let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) + definitions[key] = definition + } + // MARK: Register dependencies /** Register a Void->T factory associated with optional tag. - parameter tag: The arbitrary tag to associate this factory with when registering with that protocol. Pass `nil` to associate with any tag. Default value is `nil`. + - parameter scope: scope to use for this compone - parameter factory: The factory to register, with return type of protocol you want to register it for + - returns: definition created for provided type and factory - note: You must cast the factory return type to the protocol you want to register it for. Inside factory block if you need to reference container use it as `unowned` to avoid retain cycle. @@ -85,8 +110,8 @@ public class DependencyContainer { container.register { [unowned container] ClientImp(service: container.resolve()) as Client } ``` */ - public func register(tag tag: Tag? = nil, factory: ()->T) -> DefinitionOf { - return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf + public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: ()->T) -> DefinitionOfT> { + return registerFactory(tag: tag, scope: scope, factory: factory) } /** @@ -99,8 +124,8 @@ public class DependencyContainer { - note: You must cast the instance to the protocol you want to register it with (e.g `MyClass() as MyAPI`) */ @available(*, deprecated, message="Use inScope(:) method of DefinitionOf instead to define scope.") - public func register(tag tag: Tag? = nil, @autoclosure(escaping) instance factory: ()->T) -> DefinitionOf { - return register(tag: tag, factory: { factory() }, scope: .Singleton) + public func register(tag tag: Tag? = nil, @autoclosure(escaping) instance factory: ()->T) -> DefinitionOfT> { + return registerFactory(tag: tag, scope: .Singleton, factory: { factory() }) } /** @@ -109,23 +134,24 @@ public class DependencyContainer { - parameter tag: The arbitrary tag to look for when resolving this protocol. - parameter factory: generic factory that should be used to create concrete instance of type - parameter scope: scope of the component. Default value is `Prototype` - + - returns: definition created for provided type and factory + - note: You should not call this method directly, instead call any of other `register` methods. You _should_ use this method only to register dependency with more runtime arguments than _Dip_ supports (currently it's up to six) like in this example: ```swift - public func register(tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, ...) -> T) -> DefinitionOf { - return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf + public func register(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, ...) -> T) -> DefinitionOf T> { + return register(tag: tag, scope: scope, factory: factory) as DefinitionOf T> } ``` Though before you do that you should probably review your design and try to reduce number of depnedencies. */ - public func register(tag tag: Tag? = nil, factory: F, scope: ComponentScope) -> DefinitionOf { + public func registerFactory(tag tag: Tag? = nil, scope: ComponentScope, factory: F) -> DefinitionOf { let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) - let definition = DefinitionOf(factory: factory, scope: scope) + let definition = DefinitionOf(factory: factory, scope: scope) definitions[key] = definition return definition } @@ -150,6 +176,7 @@ public class DependencyContainer { - parameter tag: The arbitrary tag to look for when resolving this protocol. - parameter builder: Generic closure that accepts generic factory and returns inctance produced by that factory + - returns: resolved instance of type T - note: You should not call this method directly, instead call any of other `resolve` methods. (see `RuntimeArguments.swift`). You _should_ use this method only to resolve dependency with more runtime arguments than _Dip_ supports @@ -168,17 +195,17 @@ public class DependencyContainer { let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) let nilTagKey = tag.map { _ in DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: nil) } - guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf else { + guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf else { fatalError("No definition registered with \(key) or \(nilTagKey)." + "Check the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()`.") } let usingKey: DefinitionKey? = definition.scope == .ObjectGraph ? key : nil - return _resolve(usingKey, definition: definition, builder: builder) + return _resolve(tag, key: usingKey, definition: definition, builder: builder) } /// Actually resolve dependency - private func _resolve(key: DefinitionKey?, definition: DefinitionOf, builder: F->T) -> T { + private func _resolve(tag: Tag? = nil, key: DefinitionKey?, var definition: DefinitionOf, builder: F->T) -> T { return resolvedInstances.resolve { @@ -196,12 +223,14 @@ public class DependencyContainer { } resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key) + definition.resolvedInstance(self, tag: tag, instance: resolvedInstance) definition.resolveDependenciesBlock?(self, resolvedInstance) return resolvedInstance } } + } // MARK: - Private @@ -212,13 +241,12 @@ public class DependencyContainer { ///Before `resolve()` returns pool is drained. class ResolvedInstances { var resolvedInstances = [DefinitionKey: Any]() - - func storeResolvedInstance(instance: T, forKey key: DefinitionKey?, definition: DefinitionOf) { + + func storeResolvedInstance(instance: T, forKey key: DefinitionKey?) { self.resolvedInstances[key] = instance - definition.resolvedInstance = instance } - func previouslyResolved(key: DefinitionKey?, definition: DefinitionOf) -> T? { + func previouslyResolved(key: DefinitionKey?, definition: DefinitionOf) -> T? { return (definition.resolvedInstance ?? self.resolvedInstances[key]) as? T } diff --git a/Dip/Dip/RuntimeArguments.swift b/Dip/Dip/RuntimeArguments.swift index 4eccf4e..38accb2 100644 --- a/Dip/Dip/RuntimeArguments.swift +++ b/Dip/Dip/RuntimeArguments.swift @@ -43,8 +43,8 @@ extension DependencyContainer { - seealso: `register(tag:factory:scope:)` */ - public func register(tag tag: Tag? = nil, factory: (Arg1) -> T) -> DefinitionOf { - return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf + public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) -> T) -> DefinitionOf T> { + return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf T> } /** @@ -63,8 +63,8 @@ extension DependencyContainer { // MARK: 2 Runtime Arguments /// - seealso: `register(:factory:scope:)` - public func register(tag tag: Tag? = nil, factory: (Arg1, Arg2) -> T) -> DefinitionOf { - return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf + public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2) -> T) -> DefinitionOf T> { + return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf T> } /// - seealso: `resolve(tag:_:)` @@ -74,8 +74,8 @@ extension DependencyContainer { // MARK: 3 Runtime Arguments - public func register(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3) -> T) -> DefinitionOf { - return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf + public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3) -> T) -> DefinitionOf T> { + return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf T> } /// - seealso: `resolve(tag:_:)` @@ -85,8 +85,8 @@ extension DependencyContainer { // MARK: 4 Runtime Arguments - public func register(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4) -> T) -> DefinitionOf { - return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf + public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4) -> T) -> DefinitionOf T> { + return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf T> } /// - seealso: `resolve(tag:_:)` @@ -96,8 +96,8 @@ extension DependencyContainer { // MARK: 4 Runtime Arguments - public func register(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) -> T) -> DefinitionOf { - return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf + public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) -> T) -> DefinitionOf T> { + return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf T> } /// - seealso: `resolve(tag:_:)` @@ -107,8 +107,8 @@ extension DependencyContainer { // MARK: 5 Runtime Arguments - public func register(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf { - return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf + public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf T> { + return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf T> } /// - seealso: `resolve(tag:_:)` diff --git a/Dip/DipTests/ComponentScopeTests.swift b/Dip/DipTests/ComponentScopeTests.swift index 4ac3e8f..71f50e3 100644 --- a/Dip/DipTests/ComponentScopeTests.swift +++ b/Dip/DipTests/ComponentScopeTests.swift @@ -29,7 +29,7 @@ class ComponentScopeTests: XCTestCase { } func testThatCallingInScopeChangesScope() { - let def = container.register { ServiceImp1() as Service }.inScope(.Singleton) + let def = container.register(ComponentScope.Singleton) { ServiceImp1() as Service } XCTAssertEqual(def.scope, ComponentScope.Singleton) } @@ -47,7 +47,7 @@ class ComponentScopeTests: XCTestCase { func testThatItReusesInstanceForSingletonScope() { //given - container.register { ServiceImp1() as Service }.inScope(.Singleton) + container.register(.Singleton) { ServiceImp1() as Service } //when let service1 = container.resolve() as Service @@ -73,10 +73,12 @@ class ComponentScopeTests: XCTestCase { func testThatItReusesInstanceInObjectGraphScopeDuringResolve() { //given - container.register { [unowned container] in Client(server: container.resolve()) as Client }.inScope(.ObjectGraph) - container.register { Server() as Server }.resolveDependencies { container, server in + container.register(.ObjectGraph) { [unowned container] in Client(server: container.resolve()) as Client } + + var serverDefinition = container.register(.ObjectGraph) { Server() as Server } + serverDefinition.resolveDependencies(container) { container, server in server.client = container.resolve() as Client - }.inScope(.ObjectGraph) + } //when let client = container.resolve() as Client @@ -88,10 +90,11 @@ class ComponentScopeTests: XCTestCase { func testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve() { //given - container.register { [unowned container] in Client(server: container.resolve()) as Client }.inScope(.ObjectGraph) - container.register { Server() as Server }.resolveDependencies { container, server in + container.register(.ObjectGraph) { [unowned container] in Client(server: container.resolve()) as Client } + var serverDefinition = container.register(.ObjectGraph) { Server() as Server } + serverDefinition.resolveDependencies(container) { container, server in server.client = container.resolve() as Client - }.inScope(.ObjectGraph) + } //when let client = container.resolve() as Client @@ -108,10 +111,11 @@ class ComponentScopeTests: XCTestCase { func testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag() { //given var service2: Service? - container.register() { ServiceImp1() as Service }.inScope(.ObjectGraph).resolveDependencies { (c, _) in + var service1Definition = container.register(.ObjectGraph) { ServiceImp1() as Service } + service1Definition.resolveDependencies(container) { (c, _) in service2 = c.resolve(tag: "service") as Service } - container.register(tag: "service") { ServiceImp2() as Service}.inScope(.ObjectGraph) + container.register(tag: "service", .ObjectGraph) { ServiceImp2() as Service} //when let service1 = container.resolve(tag: "tag") as Service diff --git a/Dip/DipTests/DipTests.swift b/Dip/DipTests/DipTests.swift index fbdb54b..13165ef 100644 --- a/Dip/DipTests/DipTests.swift +++ b/Dip/DipTests/DipTests.swift @@ -84,7 +84,8 @@ class DipTests: XCTestCase { func testThatItCallsResolveDependenciesOnDefinition() { //given var resolveDependenciesCalled = false - container.register { ServiceImp1() as Service }.resolveDependencies { (c, s) in + var definition = container.register { ServiceImp1() as Service } + definition.resolveDependencies(container) { (c, s) in resolveDependenciesCalled = true } diff --git a/Dip/DipTests/RuntimeArgumentsTests.swift b/Dip/DipTests/RuntimeArgumentsTests.swift index 75c12e0..dc4580a 100644 --- a/Dip/DipTests/RuntimeArgumentsTests.swift +++ b/Dip/DipTests/RuntimeArgumentsTests.swift @@ -33,10 +33,10 @@ class RuntimeArgumentsTests: XCTestCase { func testThatItResolvesInstanceWithOneArgument() { //given let arg1 = 1 - container.register { (a1: Int) -> Service in + container.register(factory: { (a1: Int) -> Service in XCTAssertEqual(a1, arg1) return ServiceImp1() - } + }) //when let service = container.resolve(arg1) as Service @@ -149,8 +149,8 @@ class RuntimeArgumentsTests: XCTestCase { func testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments() { //given let arg1 = 1, arg2 = "string" - container.register { (a1: Int) in ServiceImp1() as Service } - container.register { (a1: String) in ServiceImp2() as Service } + container.register(factory: { (a1: Int) in ServiceImp1() as Service }) + container.register(factory: { (a1: String) in ServiceImp2() as Service }) //when let service1 = container.resolve(arg1) as Service From abf2202eeb4a727c255dc56195cb396c2110c037 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Fri, 20 Nov 2015 15:08:54 +0100 Subject: [PATCH 05/12] not mutating resolveDependencies method --- Dip/Dip/Definition.swift | 8 +++++--- Dip/DipTests/ComponentScopeTests.swift | 9 +++------ Dip/DipTests/DipTests.swift | 3 +-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Dip/Dip/Definition.swift b/Dip/Dip/Definition.swift index bc5ff47..e1bd7f1 100644 --- a/Dip/Dip/Definition.swift +++ b/Dip/Dip/Definition.swift @@ -81,12 +81,14 @@ public struct DefinitionOf: Definition { ``` */ - public mutating func resolveDependencies(container: DependencyContainer, tag: DependencyContainer.Tag? = nil, block: (DependencyContainer, T) -> ()) { + public func resolveDependencies(container: DependencyContainer, tag: DependencyContainer.Tag? = nil, block: (DependencyContainer, T) -> ()) -> DefinitionOf { guard resolveDependenciesBlock == nil else { fatalError("You can not change resolveDependencies block after it was set.") } - resolveDependenciesBlock = block - container.register(tag: tag, definition: self) + var newDefinition = self + newDefinition.resolveDependenciesBlock = block + container.register(tag: tag, definition: newDefinition) + return newDefinition } let factory: F diff --git a/Dip/DipTests/ComponentScopeTests.swift b/Dip/DipTests/ComponentScopeTests.swift index 71f50e3..78d39fc 100644 --- a/Dip/DipTests/ComponentScopeTests.swift +++ b/Dip/DipTests/ComponentScopeTests.swift @@ -75,8 +75,7 @@ class ComponentScopeTests: XCTestCase { //given container.register(.ObjectGraph) { [unowned container] in Client(server: container.resolve()) as Client } - var serverDefinition = container.register(.ObjectGraph) { Server() as Server } - serverDefinition.resolveDependencies(container) { container, server in + container.register(.ObjectGraph) { Server() as Server }.resolveDependencies(container) { container, server in server.client = container.resolve() as Client } @@ -91,8 +90,7 @@ class ComponentScopeTests: XCTestCase { func testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve() { //given container.register(.ObjectGraph) { [unowned container] in Client(server: container.resolve()) as Client } - var serverDefinition = container.register(.ObjectGraph) { Server() as Server } - serverDefinition.resolveDependencies(container) { container, server in + container.register(.ObjectGraph) { Server() as Server }.resolveDependencies(container) { container, server in server.client = container.resolve() as Client } @@ -111,8 +109,7 @@ class ComponentScopeTests: XCTestCase { func testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag() { //given var service2: Service? - var service1Definition = container.register(.ObjectGraph) { ServiceImp1() as Service } - service1Definition.resolveDependencies(container) { (c, _) in + container.register(.ObjectGraph) { ServiceImp1() as Service }.resolveDependencies(container) { (c, _) in service2 = c.resolve(tag: "service") as Service } container.register(tag: "service", .ObjectGraph) { ServiceImp2() as Service} diff --git a/Dip/DipTests/DipTests.swift b/Dip/DipTests/DipTests.swift index 13165ef..149e4a2 100644 --- a/Dip/DipTests/DipTests.swift +++ b/Dip/DipTests/DipTests.swift @@ -84,8 +84,7 @@ class DipTests: XCTestCase { func testThatItCallsResolveDependenciesOnDefinition() { //given var resolveDependenciesCalled = false - var definition = container.register { ServiceImp1() as Service } - definition.resolveDependencies(container) { (c, s) in + container.register { ServiceImp1() as Service }.resolveDependencies(container) { (c, s) in resolveDependenciesCalled = true } From f6afdfd08e9e34165298536e9fb2d862cda2196a Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sat, 21 Nov 2015 17:18:14 +0100 Subject: [PATCH 06/12] added tag property on DefinitionOf --- Dip/Dip/Definition.swift | 10 ++++++---- Dip/Dip/Dip.swift | 10 +++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Dip/Dip/Definition.swift b/Dip/Dip/Definition.swift index e1bd7f1..f913e24 100644 --- a/Dip/Dip/Definition.swift +++ b/Dip/Dip/Definition.swift @@ -81,23 +81,25 @@ public struct DefinitionOf: Definition { ``` */ - public func resolveDependencies(container: DependencyContainer, tag: DependencyContainer.Tag? = nil, block: (DependencyContainer, T) -> ()) -> DefinitionOf { + public func resolveDependencies(container: DependencyContainer, block: (DependencyContainer, T) -> ()) -> DefinitionOf { guard resolveDependenciesBlock == nil else { fatalError("You can not change resolveDependencies block after it was set.") } var newDefinition = self newDefinition.resolveDependenciesBlock = block - container.register(tag: tag, definition: newDefinition) + container.register(newDefinition) return newDefinition } let factory: F var scope: ComponentScope var resolveDependenciesBlock: ((DependencyContainer, T) -> ())? + let tag: DependencyContainer.Tag? - init(factory: F, scope: ComponentScope) { + init(factory: F, scope: ComponentScope, tag: DependencyContainer.Tag?) { self.factory = factory self.scope = scope + self.tag = tag } ///Will be stored only if scope is `Singleton` @@ -111,7 +113,7 @@ public struct DefinitionOf: Definition { mutating func resolvedInstance(container: DependencyContainer, tag: DependencyContainer.Tag? = nil, instance: T) { guard scope == .Singleton else { return } _resolvedInstance = instance - container.register(tag: tag, definition: self) + container.register(self) } private var _resolvedInstance: T? diff --git a/Dip/Dip/Dip.swift b/Dip/Dip/Dip.swift index 0c96f47..962b9ea 100644 --- a/Dip/Dip/Dip.swift +++ b/Dip/Dip/Dip.swift @@ -74,8 +74,8 @@ public class DependencyContainer { - parameter tag: tag used to register definition - parameter definition: definition to remove */ - public func remove(tag tag: Tag? = nil, definition: DefinitionOf) { - let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) + public func remove(definition: DefinitionOf) { + let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: definition.tag) definitions[key] = nil } @@ -86,8 +86,8 @@ public class DependencyContainer { - parameter tag: The arbitrary tag to associate definition with - parameter definition: definition to register in container */ - public func register(tag tag: Tag? = nil, definition: DefinitionOf) { - let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) + public func register(definition: DefinitionOf) { + let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: definition.tag) definitions[key] = definition } @@ -151,7 +151,7 @@ public class DependencyContainer { */ public func registerFactory(tag tag: Tag? = nil, scope: ComponentScope, factory: F) -> DefinitionOf { let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) - let definition = DefinitionOf(factory: factory, scope: scope) + let definition = DefinitionOf(factory: factory, scope: scope, tag: tag) definitions[key] = definition return definition } From 59719a0c376dfa52d52eefa365accadd4cb0d546 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sat, 21 Nov 2015 17:18:29 +0100 Subject: [PATCH 07/12] updated documentation --- CHANGELOG.md | 1 + Dip/Dip/Dip.swift | 23 ++++++++++--- Dip/Dip/RuntimeArguments.swift | 4 +-- README.md | 63 +++++++++++++++++++++------------- 4 files changed, 60 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f21a8f7..99a8f55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Removed container thread-safety to enable recursion calls to `resolve`. **Access to container from multiple threads should be handled by clients** from now on. +* Deprecated `register(tag:instance:)` method. ## 3.0.0 diff --git a/Dip/Dip/Dip.swift b/Dip/Dip/Dip.swift index 962b9ea..7a3fe88 100644 --- a/Dip/Dip/Dip.swift +++ b/Dip/Dip/Dip.swift @@ -104,10 +104,14 @@ public class DependencyContainer { - note: You must cast the factory return type to the protocol you want to register it for. Inside factory block if you need to reference container use it as `unowned` to avoid retain cycle. - **Example** + **Example**: ```swift container.register { ServiceImp() as Service } - container.register { [unowned container] ClientImp(service: container.resolve()) as Client } + container.register(tag: "service") { ServiceImp() as Service } + container.register(.ObjectGraph) { ServiceImp() as Service } + container.register { [unowned container] + ClientImp(service: container.resolve() as Service) as Client + } ``` */ public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: ()->T) -> DefinitionOfT> { @@ -122,8 +126,10 @@ public class DependencyContainer { - parameter instance: The instance to register, with return type of protocol you want to register it for - note: You must cast the instance to the protocol you want to register it with (e.g `MyClass() as MyAPI`) + + **Deprecated**: Use `register(.Singleton){}` method instead to define singleton scope. */ - @available(*, deprecated, message="Use inScope(:) method of DefinitionOf instead to define scope.") + @available(*, deprecated, message="Use `register(.Singleton){}` method instead to define singleton scope.") public func register(tag tag: Tag? = nil, @autoclosure(escaping) instance factory: ()->T) -> DefinitionOfT> { return registerFactory(tag: tag, scope: .Singleton, factory: { factory() }) } @@ -142,7 +148,7 @@ public class DependencyContainer { ```swift public func register(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, ...) -> T) -> DefinitionOf T> { - return register(tag: tag, scope: scope, factory: factory) as DefinitionOf T> + return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf T> } ``` @@ -166,6 +172,13 @@ public class DependencyContainer { - parameter tag: The arbitrary tag to look for when resolving this protocol. + **Example**: + ```swift + let service = container.resolve() as Service + let service = container.resolve(tag: "service") as Service + let service: Service = container.resolve() + ``` + */ public func resolve(tag tag: Tag? = nil) -> T { return resolve(tag: tag) { (factory: ()->T) in factory() } @@ -196,7 +209,7 @@ public class DependencyContainer { let nilTagKey = tag.map { _ in DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: nil) } guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf else { - fatalError("No definition registered with \(key) or \(nilTagKey)." + fatalError("No definition registered with " + (tag == nil ? "\(key)" : "\(key) or \(nilTagKey)") + ". " + "Check the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()`.") } diff --git a/Dip/Dip/RuntimeArguments.swift b/Dip/Dip/RuntimeArguments.swift index 38accb2..17519f1 100644 --- a/Dip/Dip/RuntimeArguments.swift +++ b/Dip/Dip/RuntimeArguments.swift @@ -41,7 +41,7 @@ extension DependencyContainer { When you resolve it container will match the type and tag as well as __number__, __types__ and __order__ of runtime arguments that you pass to `resolve` method. - - seealso: `register(tag:factory:scope:)` + - seealso: `registerFactory(tag:scope:factory:)` */ public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) -> T) -> DefinitionOf T> { return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf T> @@ -54,7 +54,7 @@ extension DependencyContainer { - parameter tag: The arbitrary tag to look for when resolving this protocol. - parameter arg1: First argument to be passed to factory - - seealso: `resolve(tag:)` + - seealso: `resolve(tag:builder:)` */ public func resolve(tag tag: Tag? = nil, _ arg1: Arg1) -> T { return resolve(tag: tag) { (factory: (Arg1) -> T) in factory(arg1) } diff --git a/README.md b/README.md index 094a536..6977b98 100644 --- a/README.md +++ b/README.md @@ -54,13 +54,13 @@ The next paragraphs give you an overview of the Usage of _Dip_ directly, but if ## Usage -### Register instances and instance factories +### Register instance factories -First, create a `DependencyContainer` and use it to register instances and factories with protocols, using those methods: +First, create a `DependencyContainer` and use it to register instance factories with protocols, using those methods: -* `register(instance: _)` will register a singleton instance with a given protocol. -* `register(factory: _)` will register an instance factory — which generates a new instance each time you `resolve()`. -* You need **cast the instance to the protocol type** you want to register it with (e.g. `register(instance: PlistUsersProvider() as UsersListProviderType)`). +* `register(.Singleton) { … }` will register a singleton instance with a given protocol. +* `register(.Prototype) { … }` or `register(.ObjectGraph) { … }` will register an instance factory which generates a new instance each time you `resolve()`. +* You need **cast the instance to the protocol type** you want to register it with (e.g. `register { PlistUsersProvider() as UsersListProviderType }`). Typically, to register your dependencies as early as possible in your app life-cycle, you will declare a `let dip: DependencyContainer = { … }()` somewhere (for example [in a dedicated `.swift` file](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/DependencyContainers.swift#L22-L27)). In your (non-hosted, standalone) unit tests, you'll probably [reset them in your `func setUp()`](https://github.com/AliSoftware/Dip/blob/master/Example/Tests/SWAPIPersonProviderTests.swift#L17-L21) instead. @@ -68,7 +68,20 @@ Typically, to register your dependencies as early as possible in your app life-c * `resolve()` will return a new instance matching the requested protocol. * Explicitly specify the return type of `resolve` so that Swift's type inference knows which protocol you're trying to resolve. -* If that protocol was registered as a singleton instance (using `register(instance: …)`, the same instance will be returned each time you call `resolve()` for this protocol type. Otherwise, the instance factory will generate a new instance each time. + +```swift +container.register { ServiceImp() as Service } +let service = container.resolve() as Service +``` + +### Scopes + +Dip provides three _scopes_ that you can use to register dependencies: + +* The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. It's a default scope. +* The `.ObjectGraph` scope is like `.Prototype` scope but it will make the `DependencyContainer` to reuse resolved instances during one call to `resolve` method. When this call returns all resolved insances will be discarded and next call to `resolve` will produce new instances. This scope should be used to resolve circular dependencies. +* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls to `resolve` during the container lifetime. + ### Using block-based initialization @@ -80,8 +93,8 @@ It may not seem to provide much, but given the fact that `DependencyContainers` let dip: DependencyContainer = { let dip = DependencyContainer() - dip.register(instance: ProductionEnvironment(analytics: true) as EnvironmentType) - dip.register(instance: WebService() as WebServiceAPI) + dip.register { ProductionEnvironment(analytics: true) as EnvironmentType } + dip.register { WebService() as WebServiceAPI } return dip }() @@ -91,8 +104,8 @@ You can instead write this exact equivalent code, which is more compact, and ind ```swift let dip = DependencyContainer { dip in - dip.register(instance: ProductionEnvironment(analytics: true) as EnvironmentType) - dip.register(instance: WebService() as WebServiceAPI) + dip.register { ProductionEnvironment(analytics: true) as EnvironmentType } + dip.register { WebService() as WebServiceAPI } } ``` @@ -111,8 +124,8 @@ enum WebService: String { } let wsDependencies = DependencyContainer() { dip in - dip.register(tag: WebService.PersonWS.tag, instance: URLSessionNetworkLayer(baseURL: "http://prod.myapi.com/api/")! as NetworkLayer) - dip.register(tag: WebService.StashipWS.tag, instance: URLSessionNetworkLayer(baseURL: "http://dev.myapi.com/api/")! as NetworkLayer) + dip.register(tag: WebService.PersonWS.tag) { URLSessionNetworkLayer(baseURL: "http://prod.myapi.com/api/")! as NetworkLayer } + dip.register(tag: WebService.StashipWS.tag) { URLSessionNetworkLayer(baseURL: "http://dev.myapi.com/api/")! as NetworkLayer } } let networkLayer = dip.resolve(tag: WebService.PersonWS.tag) as NetworkLayer @@ -137,8 +150,8 @@ let service3 = webServices.resolve(80, NSURL(string: "http://example.url")) as W Though Dip provides support for up to six runtime arguments out of the box you can extend this number using following code snippet for seven arguments: ``` -func register(tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T) -> DefinitionOf { - return register(tag, factory: factory, scope: .Prototype) as DefinitionOf +func register(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T) -> DefinitionOf T)> { + return registerFactory(tag, scope: .Prototype, factory: factory) as DefinitionOf T)> } func resolve(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) -> T { @@ -149,17 +162,19 @@ func resolve(tag tag: Tag? = nil, _ ### Circular dependencies -_Dip_ supports circular dependencies. To resolve them use new `ObjectGraph` scope and `resolveDependencies` method of `DefinitionOf` returned by `register` method. +_Dip_ supports circular dependencies. To resolve them use `ObjectGraph` scope and `resolveDependencies` method of `DefinitionOf` returned by `register` method. ```swift -container.register { [unowned container] ClientImp(server: container.resolve() as Server) as Client }.inScope(.ObjectGraph) - -container.register { ServerImp() as Server } -.inScope(.ObjectGraph) -.resolveDependencies { container, server in - server.client = container.resolve() as Client +container.register(.ObjectGraph) { [unowned container] in + ClientImp(server: container.resolve() as Server) as Client } + +container.register(.ObjectGraph) { ServerImp() as Server } + .resolveDependencies { container, server in + server.client = container.resolve() as Client + } ``` +More infromation about circular dependencies you can find in a playground. ### Thread safety @@ -174,8 +189,8 @@ Somewhere in your App target, register the dependencies: let dip: DependencyContainer = { let dip = DependencyContainer() let env = ProductionEnvironment(analytics: true) - dip.register(instance: env as EnvironmentType) - dip.register(instance: WebService() as WebServiceType) + dip.register(.Singleton) { env as EnvironmentType } + dip.register(.Singleton) { WebService() as WebServiceType } dip.register() { (name: String) in DummyFriendsProvider(user: name) as FriendsProviderType } dip.register(tag: "me") { (_: String) in PlistFriendsProvider(plist: "myfriends") as FriendsProviderType } return dip @@ -229,7 +244,7 @@ This sample uses the Star Wars API provided by swapi.co to fetch Star Wars chara ## Credits This library has been created by [**Olivier Halligon**](olivier@halligon.net). -I'd also like to thank **Ilya Puchka** for his big contribution to it, as he added a lot of great features to it. +I'd also like to thank [**Ilya Puchka**](https://twitter.com/ilyapuchka) for his big contribution to it, as he added a lot of great features to it. **Dip** is available under the **MIT license**. See the `LICENSE` file for more info. From 450a2f8a72968be0d7695047c4b296b69f6e8ac0 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sat, 21 Nov 2015 17:19:51 +0100 Subject: [PATCH 08/12] updated playground and added page about circular dependencies --- CHANGELOG.md | 2 + .../Contents.swift | 87 +++++++++++++++++++ .../timeline.xctimeline | 6 ++ .../Contents.swift | 7 ++ .../Contents.swift | 20 ++--- .../Scopes.xcplaygroundpage/Contents.swift | 35 +++++--- .../Contents.swift | 4 +- DipPlayground.playground/Sources/Models.swift | 12 ++- .../contents.xcplayground | 3 +- 9 files changed, 149 insertions(+), 27 deletions(-) create mode 100644 DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/Contents.swift create mode 100644 DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/timeline.xctimeline diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a8f55..6e3eb69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ * Added support for circular dependencies by adding `ObjectGraph` scope to reuse resolved instances. [#11](https://github.com/AliSoftware/Dip/pull/11), [@ilyapuchka](https://github.com/ilyapuchka) +* Added methods to register/remove individual definitions. + [#11](https://github.com/AliSoftware/Dip/pull/11), [@ilyapuchka](https://github.com/ilyapuchka) #### Breaking Changes diff --git a/DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000..91292b5 --- /dev/null +++ b/DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/Contents.swift @@ -0,0 +1,87 @@ +//: [Previous: Scopes](@previous) + +import Dip + +let container = DependencyContainer() + +/*: +### Circular Dependencies + +Very often we encounter situations when we have circular dependencies between components. The most obvious example is delegation pattern. Dip can resolve such dependencies easily. + +Let's say you have some network client and it's delegate defined like this: +*/ + +protocol NetworkClientDelegate: class { + var networkClient: NetworkClient { get } +} + +protocol NetworkClient: class { + weak var delegate: NetworkClientDelegate? {get set} +} + +class NetworkClientImp: NetworkClient { + weak var delegate: NetworkClientDelegate? + init() {} +} + +class Interactor: NetworkClientDelegate { + let networkClient: NetworkClient + init(networkClient: NetworkClient) { + self.networkClient = networkClient + } +} + +/*: +Note that one of this classes uses _property injection_ (`NetworkClientImp`) and another uses _constructor injection_ (`Interactor`). +It's very important that _at least one_ of them uses property injection, 'cause if you try to use constructor injection for both of them then you will enter infinite loop when you will call `resolve`. + +Now you can register those classes in container: +*/ + +container.register(.ObjectGraph) { [unowned container] in + Interactor(networkClient: container.resolve()) as NetworkClientDelegate +} + +container.register(.ObjectGraph) { NetworkClientImp() as NetworkClient } + .resolveDependencies(container) { (container, client) -> () in + client.delegate = container.resolve() as NetworkClientDelegate +} + +/*: +Here you can spot the difference in the way we register classes. `Interactor` class uses constructor injection so to regiter it we use block factory where we call `resolve` to obtain instance of `NetworkClient` and pass it to constructor. `NetworkClientImp` uses property injection for it's delegate property. Again we use block factory to create instance, but to inject delegate property we use special `resolveDependencies` method. Block passed to this method will be called right _after_ block factory. So you can use this block to perform additional setup or, like in this example, to resolve circular dependencies. This way `DependencyContainer` breaks infinite recursion that would happen if we used constructor injection for both of our components. + +*Note*: Capturing container as `unowned` reference is important to avoid retain cycle between container and definition. + +Now when you resolve `NetworkClientDelegate` you will get instance of `Interactor` that will have client with delegate referencing the same `Interactor` instance: +*/ + +let interactor = container.resolve() as NetworkClientDelegate +interactor.networkClient.delegate === interactor + +/*: +**Warning**: Note that one of the properties (`delegate`) is defined as _weak_. That's crucial to avoid retain cycle. But now if you try to resolve `NetworkClient` first it's delegate will be released before `resolve` returns, 'cuase no one holds a reference to it except the container. +*/ + +let networkClient = container.resolve() as NetworkClient +networkClient.delegate // delegate was alread released =( + +/*: +Note also that we used `.ObjectGraph` scope to register implementations. This is also very important to preserve consistency of objects relationships. If we would have used `.Prototype` scope for both components then container would not reuse instances and we would have infinite loop. Each attemp to resolve `NetworkClientDelegate` will create new instance of `Interactor`. It will resolve `NetworkClient` which will create new instance of `NetworkClientImp`. It will try to resolve it's delegate property and that will create new instance of `Interactor`. And so on and so on. If we would have used `.Prototype` for one of the components it will lead to the same infinite loop or one of the relationships will be invalid: +*/ + +container.reset() + +container.register(.Prototype) { [unowned container] in + Interactor(networkClient: container.resolve()) as NetworkClientDelegate +} + +container.register(.ObjectGraph) { NetworkClientImp() as NetworkClient } + .resolveDependencies(container) { (container, client) -> () in + client.delegate = container.resolve() as NetworkClientDelegate +} + +let invalidInteractor = container.resolve() as NetworkClientDelegate +invalidInteractor.networkClient.delegate // that is not valid + +//: [Next: Shared Instances](@next) diff --git a/DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/timeline.xctimeline b/DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/timeline.xctimeline new file mode 100644 index 0000000..bf468af --- /dev/null +++ b/DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/timeline.xctimeline @@ -0,0 +1,6 @@ + + + + + diff --git a/DipPlayground.playground/Pages/Registering components.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Registering components.xcplaygroundpage/Contents.swift index dc0a9bd..2651fb7 100644 --- a/DipPlayground.playground/Pages/Registering components.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Registering components.xcplaygroundpage/Contents.swift @@ -39,6 +39,13 @@ container.register(tag: DependencyContainer.Tag.Int(0)) { ServiceImp1() as Servi /*: We recommand you to use constants for the tags, to make the intent clear and avoid magic numbers and typos. + +You can remove all registered definitions or register and remove them one by one: */ +let serviceDefinition = container.register { ServiceImp1() as Service } +container.remove(serviceDefinition) + +container.reset() + //: [Next: Resolving Components](@next) diff --git a/DipPlayground.playground/Pages/Runtime arguments.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Runtime arguments.xcplaygroundpage/Contents.swift index 3f9f0c9..e9801e1 100644 --- a/DipPlayground.playground/Pages/Runtime arguments.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Runtime arguments.xcplaygroundpage/Contents.swift @@ -12,10 +12,10 @@ Dip lets you use runtime arguments to register and resolve your components. Note that __types__, __number__ and __order__ of arguments matters and you can register different factories with different set of runtime arguments for the same protocol. To resolve using one of this factory you will need to pass runtime arguments of the same types, number and in the same order to `resolve` as you used in `register` method. */ -container.register { (url: NSURL, port: Int) in ServiceImp3(name: "1", baseURL: url, port: port) as Service } -container.register { (port: Int, url: NSURL) in ServiceImp3(name: "2", baseURL: url, port: port) as Service } -container.register { (port: Int, url: NSURL?) in ServiceImp3(name: "3", baseURL: url!, port: port) as Service } -container.register { (port: Int, url: NSURL!) in ServiceImp3(name: "4", baseURL: url, port: port) as Service } +container.register { (url: NSURL, port: Int) in ServiceImp4(name: "1", baseURL: url, port: port) as Service } +container.register { (port: Int, url: NSURL) in ServiceImp4(name: "2", baseURL: url, port: port) as Service } +container.register { (port: Int, url: NSURL?) in ServiceImp4(name: "3", baseURL: url!, port: port) as Service } +container.register { (port: Int, url: NSURL!) in ServiceImp4(name: "4", baseURL: url, port: port) as Service } let url: NSURL = NSURL(string: "http://example.com")! let service1 = container.resolve(url, 80) as Service @@ -23,10 +23,10 @@ let service2 = container.resolve(80, url) as Service let service3 = container.resolve(80, NSURL(string: "http://example.com")) as Service let service4 = container.resolve(80, NSURL(string: "http://example.com")! as NSURL!) as Service -(service1 as! ServiceImp3).name -(service2 as! ServiceImp3).name -(service3 as! ServiceImp3).name -(service4 as! ServiceImp3).name +(service1 as! ServiceImp4).name +(service2 as! ServiceImp4).name +(service3 as! ServiceImp4).name +(service4 as! ServiceImp4).name /*: Note that all of the services were resolved using different factories. @@ -35,8 +35,8 @@ _Dip_ supports up to six runtime arguments. If that is not enougth you can exten */ extension DependencyContainer { - public func register(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf { - return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf + public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf T> { + return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf T> } public func resolve(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> T { diff --git a/DipPlayground.playground/Pages/Scopes.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Scopes.xcplaygroundpage/Contents.swift index de4b8fc..e3a6f81 100644 --- a/DipPlayground.playground/Pages/Scopes.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Scopes.xcplaygroundpage/Contents.swift @@ -2,30 +2,41 @@ import Dip +let container = DependencyContainer() + /*: ### Scopes -Dip supports two different scopes of objects: _Prototype_ and _Singleton_. +Dip supports three different scopes of objects: _Prototype_, _ObjectGraph_ and _Singleton_. * The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. -* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls during the container lifetime. +* The `.ObjectGraph` scope is like `.Prototype` scope but it will make the `DependencyContainer` to reuse resolved instances during one call to `resolve` method. When this call returns all resolved insances will be discarded and next call to `resolve` will produce new instances. This scope should be used to resolve [circular dependencies](Circular%20dependencies). +* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls to `resolve` during the container lifetime. -The `.Prototype` scope is the default. To register a singleton, use `register(tag:instance:)` +The `.Prototype` scope is the default. To set a scope you pass it as an argument to `register` method. */ -let container = DependencyContainer { container in - container.register(tag:"sharedService", instance: ServiceImp1() as Service) - container.register { ServiceImp1() as Service } -} - -let sharedService = container.resolve(tag: "sharedService") as Service -let sameSharedService = container.resolve(tag: "sharedService") as Service -sharedService as! ServiceImp1 === sameSharedService as! ServiceImp1 +container.register { ServiceImp1() as Service } +container.register(tag: "prototype", .Prototype) { ServiceImp1() as Service } +container.register(tag: "object graph", .ObjectGraph) { ServiceImp2() as Service } +container.register(tag: "shared instance", .Singleton) { ServiceImp3() as Service } let service = container.resolve() as Service let anotherService = container.resolve() as Service service as! ServiceImp1 === anotherService as! ServiceImp1 -//: [Next: Shared Instances](@next) +let prototypeService = container.resolve(tag: "prototype") as Service +let anotherPrototypeService = container.resolve(tag: "prototype") as Service +prototypeService as! ServiceImp1 === anotherPrototypeService as! ServiceImp1 + +let graphService = container.resolve(tag: "object graph") as Service +let anotherGraphService = container.resolve(tag: "object graph") as Service +graphService as! ServiceImp2 === anotherGraphService as! ServiceImp2 + +let sharedService = container.resolve(tag: "shared instance") as Service +let sameSharedService = container.resolve(tag: "shared instance") as Service +sharedService as! ServiceImp3 === sameSharedService as! ServiceImp3 + +//: [Next: Circular Dependencies](@next) diff --git a/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift index 39f813f..2b579be 100644 --- a/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift @@ -43,7 +43,7 @@ Sure, this is very easy to code indeed. And nothing bad so far. But probably if you wrote a unit test or integration test for that code first, you would have noticed a problem earilier. How you test that code? And how you ensure that your tests are idenpendent of the API client's state from the previous test? Of cource you can work around all of the problems and the fact that `ApiClient` is a singleton, reset it's state somehow, or mock a class so that it will not return a singleton instance. But look - a moment before the singleton was your best friend and now you are fighting against it. -Think - why do you want API client to be a singleton in a first place? To queue or throttle requests? Then do your queue or throttler a singleton, not an API client. Or is there any other reason. Most likely API client itself does not have a requirement to have only one system during the whole lifecycle of your application. Imagine that in the future we need two API Clients, because you now have to address two different servers & plaforms? Imposing that singleton restricts now your flexibility a lot. +Think - why do you want API client to be a singleton in a first place? To queue or throttle requests? Then do your queue or throttler a singleton, not an API client. Or is there any other reason. Most likely API client itself does not have a requirement to have one and only one instance during the lifecycle of your application. Imagine that in the future we need two API Clients, because you now have to address two different servers & plaforms? Imposing that singleton restricts now your flexibility a lot. Instead, inject API client in view controller with property injection or constructor injection. */ @@ -125,7 +125,7 @@ class DipViewController: UIViewController { var dipController = DipViewController(dependencies: container) /*: -Of cource `DependencyContainer` should not be used as singleton too. Instead, inject it to objects that need to access it. And use a protocol for that. For example if your view controller needs to access API client, it does not need a reference to `DependencyContainer`, it only needs a reference to _something_ that can provide it an API client instance. +Of cource `DependencyContainer` should not be a singleton too. Instead, inject it to objects that need to access it. And use a protocol for that. For example if your view controller needs to access API client, it does not need a reference to `DependencyContainer`, it only needs a reference to _something_ that can provide it an API client instance. */ protocol ApiClientProvider { diff --git a/DipPlayground.playground/Sources/Models.swift b/DipPlayground.playground/Sources/Models.swift index 9cb2806..6182743 100644 --- a/DipPlayground.playground/Sources/Models.swift +++ b/DipPlayground.playground/Sources/Models.swift @@ -8,8 +8,11 @@ public class ServiceImp1: Service { public class ServiceImp2: Service { public init() {} } - public class ServiceImp3: Service { + public init() {} +} + +public class ServiceImp4: Service { public let name: String @@ -19,7 +22,7 @@ public class ServiceImp3: Service { } -public protocol Client { +public protocol Client: class { var service: Service {get} init(service: Service) } @@ -45,3 +48,8 @@ public class ServiceFactory { } } +public class ClientServiceImp: Service { + public weak var client: Client? + public init() {} +} + diff --git a/DipPlayground.playground/contents.xcplayground b/DipPlayground.playground/contents.xcplayground index bef06c0..ca67a5f 100644 --- a/DipPlayground.playground/contents.xcplayground +++ b/DipPlayground.playground/contents.xcplayground @@ -1,5 +1,5 @@ - + @@ -7,6 +7,7 @@ + From adde50a3d0ba779b1216ad73e11602a855ebfd9f Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sat, 21 Nov 2015 17:20:19 +0100 Subject: [PATCH 09/12] added references to articles about service locator --- .../Pages/Shared Instances.xcplaygroundpage/Contents.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift index 2b579be..1f5f2a4 100644 --- a/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift @@ -150,7 +150,7 @@ dipController = DipViewController(apiClientProvider: container) /*: This way you also does not depend directly on Dip. Instead you provide a boundary between Dip — that you don't have control of — and your source code. So when something chagnes in Dip, you update only the boundary code. -Dependency injection is a pattern as well as singleton. And any pattern can be abused. That's why if you adopt DI in one part of your system it does not mean that you should inject everything and everywhere. The same with using protocols instead of concrete implementations. For every tool there is a right time and the same way as singleton can harm you the same way DI and protocols abuse can make your code unnececerry complex. +Dependency injection is a pattern as well as singleton. And any pattern can be abused. DI can be use in a [wrong way]((http://www.loosecouplings.com/2011/01/dependency-injection-using-di-container.html)), container can easily become a [service locator](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). That's why if you adopt DI in one part of your system it does not mean that you should inject everything and everywhere. The same with using protocols instead of concrete implementations. For every tool there is a right time and the same way as singleton can harm you the same way DI and protocols abuse can make your code unnececerry complex. */ From 948b3cd7804d62d2bc1ebc5e45f9fa4e8f96a24f Mon Sep 17 00:00:00 2001 From: Olivier Halligon Date: Sun, 22 Nov 2015 15:16:21 +0100 Subject: [PATCH 10/12] [Playground] Nitpicking on the Circular Dependencies page --- .../Contents.swift | 34 ++++++++++++++----- .../Scopes.xcplaygroundpage/Contents.swift | 15 +++++--- .../Contents.swift | 4 ++- .../contents.xcplayground | 2 +- 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/Contents.swift index 91292b5..f7d9d80 100644 --- a/DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/Contents.swift @@ -17,7 +17,7 @@ protocol NetworkClientDelegate: class { } protocol NetworkClient: class { - weak var delegate: NetworkClientDelegate? {get set} + weak var delegate: NetworkClientDelegate? { get set } } class NetworkClientImp: NetworkClient { @@ -33,8 +33,12 @@ class Interactor: NetworkClientDelegate { } /*: -Note that one of this classes uses _property injection_ (`NetworkClientImp`) and another uses _constructor injection_ (`Interactor`). -It's very important that _at least one_ of them uses property injection, 'cause if you try to use constructor injection for both of them then you will enter infinite loop when you will call `resolve`. +Note that: + + - one of this classes uses _property injection_ (`NetworkClientImp`) — you'll give the `delegate` value via its property directly, _after_ initialization + - and another uses _constructor injection_ (`Interactor`) — you'll need to give the `networkclient` value via the constructor, _during_ initialization. + +It's very important that _at least one_ of them uses property injection, because if you try to use constructor injection for both of them then you will enter infinite loop when you will call `resolve`. Now you can register those classes in container: */ @@ -49,25 +53,39 @@ container.register(.ObjectGraph) { NetworkClientImp() as NetworkClient } } /*: -Here you can spot the difference in the way we register classes. `Interactor` class uses constructor injection so to regiter it we use block factory where we call `resolve` to obtain instance of `NetworkClient` and pass it to constructor. `NetworkClientImp` uses property injection for it's delegate property. Again we use block factory to create instance, but to inject delegate property we use special `resolveDependencies` method. Block passed to this method will be called right _after_ block factory. So you can use this block to perform additional setup or, like in this example, to resolve circular dependencies. This way `DependencyContainer` breaks infinite recursion that would happen if we used constructor injection for both of our components. +Here you can spot the difference in the way we register classes. + + - `Interactor` class uses constructor injection, so to register it we use the block factory where we call `resolve` to obtain instance of `NetworkClient` and pass it to constructor. + - `NetworkClientImp` uses property injection for it's delegate property. Again we use block factory to create instance, but to inject the delegate property we use the special `resolveDependencies` method. Block passed to this method will be called right _after_ the block factory. So you can use this block to perform additional setup or, like in this example, to resolve circular dependencies. + +This way `DependencyContainer` breaks infinite recursion that would happen if we used constructor injection for both of our components. *Note*: Capturing container as `unowned` reference is important to avoid retain cycle between container and definition. -Now when you resolve `NetworkClientDelegate` you will get instance of `Interactor` that will have client with delegate referencing the same `Interactor` instance: +Now when you resolve `NetworkClientDelegate` you will get an instance of `Interactor` that will have client with delegate referencing the same `Interactor` instance: */ let interactor = container.resolve() as NetworkClientDelegate -interactor.networkClient.delegate === interactor +interactor.networkClient.delegate === interactor // true: they are the same instances /*: -**Warning**: Note that one of the properties (`delegate`) is defined as _weak_. That's crucial to avoid retain cycle. But now if you try to resolve `NetworkClient` first it's delegate will be released before `resolve` returns, 'cuase no one holds a reference to it except the container. +**Warning**: Note that one of the properties (`delegate`) is defined as _weak_. That's crucial to avoid retain cycle. But now if you try to resolve `NetworkClient` first it's delegate will be released before `resolve` returns, bcause no one holds a reference to it except the container. */ let networkClient = container.resolve() as NetworkClient networkClient.delegate // delegate was alread released =( /*: -Note also that we used `.ObjectGraph` scope to register implementations. This is also very important to preserve consistency of objects relationships. If we would have used `.Prototype` scope for both components then container would not reuse instances and we would have infinite loop. Each attemp to resolve `NetworkClientDelegate` will create new instance of `Interactor`. It will resolve `NetworkClient` which will create new instance of `NetworkClientImp`. It will try to resolve it's delegate property and that will create new instance of `Interactor`. And so on and so on. If we would have used `.Prototype` for one of the components it will lead to the same infinite loop or one of the relationships will be invalid: +Note also that we used `.ObjectGraph` scope to register implementations. This is also very important to preserve consistency of objects relationships. + +If we would have used `.Prototype` scope for both components then container would not reuse instances and we would have an infinite loop: + + - Each attempt to resolve `NetworkClientDelegate` will create new instance of `Interactor`. + - It will resolve `NetworkClient` which will create new instance of `NetworkClientImp`. + - It will try to resolve it's delegate property and that will create new instance of `Interactor` + - … And so on and so on. + +If we would have used `.Prototype` for one of the components it will lead to the same infinite loop or one of the relationships will be invalid: */ container.reset() diff --git a/DipPlayground.playground/Pages/Scopes.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Scopes.xcplaygroundpage/Contents.swift index e3a6f81..53d6fd8 100644 --- a/DipPlayground.playground/Pages/Scopes.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Scopes.xcplaygroundpage/Contents.swift @@ -10,8 +10,8 @@ let container = DependencyContainer() Dip supports three different scopes of objects: _Prototype_, _ObjectGraph_ and _Singleton_. -* The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. -* The `.ObjectGraph` scope is like `.Prototype` scope but it will make the `DependencyContainer` to reuse resolved instances during one call to `resolve` method. When this call returns all resolved insances will be discarded and next call to `resolve` will produce new instances. This scope should be used to resolve [circular dependencies](Circular%20dependencies). +* The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. This is the default scope. +* The `.ObjectGraph` scope is like `.Prototype` scope, but it will make the `DependencyContainer` to reuse resolved instances during one (recursive) call to `resolve` method. When this call returns, all resolved instances will be discarded and next call to `resolve` will produce new instances. This scope should be used to resolve [circular dependencies](Circular%20dependencies). * The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls to `resolve` during the container lifetime. The `.Prototype` scope is the default. To set a scope you pass it as an argument to `register` method. @@ -24,18 +24,23 @@ container.register(tag: "shared instance", .Singleton) { ServiceImp3() as Servic let service = container.resolve() as Service let anotherService = container.resolve() as Service -service as! ServiceImp1 === anotherService as! ServiceImp1 +// They are different instances as the scope defaults to .Prototype +service as! ServiceImp1 === anotherService as! ServiceImp1 // false let prototypeService = container.resolve(tag: "prototype") as Service let anotherPrototypeService = container.resolve(tag: "prototype") as Service -prototypeService as! ServiceImp1 === anotherPrototypeService as! ServiceImp1 +// They are different instances: +prototypeService as! ServiceImp1 === anotherPrototypeService as! ServiceImp1 // false let graphService = container.resolve(tag: "object graph") as Service let anotherGraphService = container.resolve(tag: "object graph") as Service -graphService as! ServiceImp2 === anotherGraphService as! ServiceImp2 +// still different instances — the ObjectGraph scope only keep instances during one (recursive) resolution call, +// so the two calls on the two lines above are different calls and use different instances +graphService as! ServiceImp2 === anotherGraphService as! ServiceImp2 // false let sharedService = container.resolve(tag: "shared instance") as Service let sameSharedService = container.resolve(tag: "shared instance") as Service +// same instances, the singleton scope keep and reuse instances during the lifetime of the container sharedService as! ServiceImp3 === sameSharedService as! ServiceImp3 //: [Next: Circular Dependencies](@next) diff --git a/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift index 1f5f2a4..8092bf9 100644 --- a/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift @@ -1,4 +1,4 @@ -//: [Previous: Scopes](@previous) +//: [Previous: Circular Dependencies](@previous) import Dip import UIKit @@ -20,6 +20,8 @@ Dip supports singletons, but it reduces cost of using them. Their singleton natu - You can easyly change concrete implementations without the rest of your system even notice that something changed. - Also it's easy to test - you just register another object in your tests. Even if you still want to use a singleton in your system. +Those features you got when using Dip limits tight coupling in your code and gives you back your code flexibility. + Probably the most common example is using a singleton in the network layer or "API client". */ diff --git a/DipPlayground.playground/contents.xcplayground b/DipPlayground.playground/contents.xcplayground index ca67a5f..c8e37c5 100644 --- a/DipPlayground.playground/contents.xcplayground +++ b/DipPlayground.playground/contents.xcplayground @@ -1,5 +1,5 @@ - + From 6c56e12eeb79c71cb5a821fa919b809434909e9e Mon Sep 17 00:00:00 2001 From: Olivier Halligon Date: Sun, 22 Nov 2015 15:58:43 +0100 Subject: [PATCH 11/12] Including whole MIT licence in comment headers As license says: "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software." ;-) --- Dip/Dip/Dip.h | 23 ++++++++++++++++++++--- Dip/DipTests/ComponentScopeTests.swift | 23 ++++++++++++++++++++--- Dip/DipTests/DipTests.swift | 23 ++++++++++++++++++++--- Dip/DipTests/RuntimeArgumentsTests.swift | 23 ++++++++++++++++++++--- SampleApp/DipSampleApp/Main.storyboard | 4 ++-- 5 files changed, 82 insertions(+), 14 deletions(-) diff --git a/Dip/Dip/Dip.h b/Dip/Dip/Dip.h index 5e4265c..d06bdee 100644 --- a/Dip/Dip/Dip.h +++ b/Dip/Dip/Dip.h @@ -1,8 +1,25 @@ // -// Dip.h -// Dip +// Dip // -// This code is under MIT Licence. See the LICENCE file for more info. +// Copyright (c) 2015 Olivier Halligon +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. // #import diff --git a/Dip/DipTests/ComponentScopeTests.swift b/Dip/DipTests/ComponentScopeTests.swift index 78d39fc..96b6f67 100644 --- a/Dip/DipTests/ComponentScopeTests.swift +++ b/Dip/DipTests/ComponentScopeTests.swift @@ -1,8 +1,25 @@ // -// RuntimeArgumentsTests.swift -// DipTests +// Dip // -// This code is under MIT Licence. See the LICENCE file for more info. +// Copyright (c) 2015 Olivier Halligon +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. // import XCTest diff --git a/Dip/DipTests/DipTests.swift b/Dip/DipTests/DipTests.swift index 149e4a2..181600b 100644 --- a/Dip/DipTests/DipTests.swift +++ b/Dip/DipTests/DipTests.swift @@ -1,8 +1,25 @@ // -// DipTests.swift -// DipTests +// Dip // -// This code is under MIT Licence. See the LICENCE file for more info. +// Copyright (c) 2015 Olivier Halligon +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. // import XCTest diff --git a/Dip/DipTests/RuntimeArgumentsTests.swift b/Dip/DipTests/RuntimeArgumentsTests.swift index dc4580a..e92d55b 100644 --- a/Dip/DipTests/RuntimeArgumentsTests.swift +++ b/Dip/DipTests/RuntimeArgumentsTests.swift @@ -1,8 +1,25 @@ // -// RuntimeArgumentsTests.swift -// DipTests +// Dip // -// This code is under MIT Licence. See the LICENCE file for more info. +// Copyright (c) 2015 Olivier Halligon +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. // import XCTest diff --git a/SampleApp/DipSampleApp/Main.storyboard b/SampleApp/DipSampleApp/Main.storyboard index 247df77..3ecec98 100644 --- a/SampleApp/DipSampleApp/Main.storyboard +++ b/SampleApp/DipSampleApp/Main.storyboard @@ -1,8 +1,8 @@ - + - + From 37d42281c1fa3c2a40203a3f5a88ecddd32665b2 Mon Sep 17 00:00:00 2001 From: Olivier Halligon Date: Sun, 22 Nov 2015 16:51:32 +0100 Subject: [PATCH 12/12] CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e3eb69..c1bfd95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ * Removed container thread-safety to enable recursion calls to `resolve`. **Access to container from multiple threads should be handled by clients** from now on. -* Deprecated `register(tag:instance:)` method. +* Deprecated `register(tag:instance:)` method in favor of `register(.Singleton, …)`. ## 3.0.0