From 4b0554b53919a6abcf76cf0a9ecbed5566b5d0fc Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Thu, 5 Nov 2015 19:15:36 +0100 Subject: [PATCH 1/7] auto-injection --- Dip/Dip.xcodeproj/project.pbxproj | 18 ++ Dip/Dip/AutoInjection.swift | 241 +++++++++++++++++++++++++ Dip/Dip/Definition.swift | 65 ++++++- Dip/Dip/Dip.swift | 81 ++++++--- Dip/DipTests/AutoInjectionTests.swift | 188 +++++++++++++++++++ Dip/DipTests/ComponentScopeTests.swift | 10 +- 6 files changed, 560 insertions(+), 43 deletions(-) create mode 100644 Dip/Dip/AutoInjection.swift create mode 100644 Dip/DipTests/AutoInjectionTests.swift diff --git a/Dip/Dip.xcodeproj/project.pbxproj b/Dip/Dip.xcodeproj/project.pbxproj index bedb545..74e238b 100644 --- a/Dip/Dip.xcodeproj/project.pbxproj +++ b/Dip/Dip.xcodeproj/project.pbxproj @@ -38,6 +38,13 @@ 0919F4EC1C16419500DC3B10 /* DefinitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */; }; 0919F4ED1C16419500DC3B10 /* RuntimeArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */; }; 0919F4EE1C16419500DC3B10 /* ComponentScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */; }; + 09873F561C1E0237000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; }; + 09873F771C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */; }; + 09873F781C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */; }; + 09873F791C1E024F000C02F6 /* AutoInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */; }; + 09873F7A1C1E0252000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; }; + 09873F7B1C1E0253000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; }; + 09873F7C1C1E0254000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -82,6 +89,8 @@ 0919F4D01C16417000DC3B10 /* DipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DipTests.swift; sourceTree = ""; }; 0919F4D11C16417000DC3B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeArgumentsTests.swift; sourceTree = ""; }; + 09873F551C1E0237000C02F6 /* AutoInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInjection.swift; sourceTree = ""; }; + 09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInjectionTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -147,6 +156,7 @@ 0919F4CA1C16417000DC3B10 /* Dip.swift */, 0919F4C81C16417000DC3B10 /* Definition.swift */, 0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */, + 09873F551C1E0237000C02F6 /* AutoInjection.swift */, 0919F4CB1C16417000DC3B10 /* Info.plist */, ); path = Dip; @@ -159,6 +169,7 @@ 0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */, 0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */, 0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */, + 09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */, 0919F4D11C16417000DC3B10 /* Info.plist */, ); path = DipTests; @@ -468,6 +479,7 @@ files = ( 0919F4D51C16417B00DC3B10 /* Definition.swift in Sources */, 0919F4D41C16417B00DC3B10 /* Dip.swift in Sources */, + 09873F561C1E0237000C02F6 /* AutoInjection.swift in Sources */, 0919F4D61C16417B00DC3B10 /* RuntimeArguments.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -480,6 +492,7 @@ 0919F4E41C16419300DC3B10 /* DefinitionTests.swift in Sources */, 0919F4E31C16419300DC3B10 /* DipTests.swift in Sources */, 0919F4E51C16419300DC3B10 /* RuntimeArgumentsTests.swift in Sources */, + 09873F771C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -489,6 +502,7 @@ files = ( 0919F4D91C16417C00DC3B10 /* Definition.swift in Sources */, 0919F4D81C16417C00DC3B10 /* Dip.swift in Sources */, + 09873F7A1C1E0252000C02F6 /* AutoInjection.swift in Sources */, 0919F4DA1C16417C00DC3B10 /* RuntimeArguments.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -501,6 +515,7 @@ 0919F4E81C16419400DC3B10 /* DefinitionTests.swift in Sources */, 0919F4E71C16419400DC3B10 /* DipTests.swift in Sources */, 0919F4E91C16419400DC3B10 /* RuntimeArgumentsTests.swift in Sources */, + 09873F781C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -510,6 +525,7 @@ files = ( 0919F4DD1C16417D00DC3B10 /* Definition.swift in Sources */, 0919F4DC1C16417D00DC3B10 /* Dip.swift in Sources */, + 09873F7B1C1E0253000C02F6 /* AutoInjection.swift in Sources */, 0919F4DE1C16417D00DC3B10 /* RuntimeArguments.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -522,6 +538,7 @@ 0919F4EC1C16419500DC3B10 /* DefinitionTests.swift in Sources */, 0919F4EB1C16419500DC3B10 /* DipTests.swift in Sources */, 0919F4ED1C16419500DC3B10 /* RuntimeArgumentsTests.swift in Sources */, + 09873F791C1E024F000C02F6 /* AutoInjectionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -531,6 +548,7 @@ files = ( 0919F4E11C16417E00DC3B10 /* Definition.swift in Sources */, 0919F4E01C16417E00DC3B10 /* Dip.swift in Sources */, + 09873F7C1C1E0254000C02F6 /* AutoInjection.swift in Sources */, 0919F4E21C16417E00DC3B10 /* RuntimeArguments.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Dip/Dip/AutoInjection.swift b/Dip/Dip/AutoInjection.swift new file mode 100644 index 0000000..8072a00 --- /dev/null +++ b/Dip/Dip/AutoInjection.swift @@ -0,0 +1,241 @@ +// +// Dip +// +// 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. +// + +//MARK: Public + +/** +Use this wrapper to identifiy strong properties of the instance that should be injected when you call +`resolveDependencies()` on this instance. Type T can be any type. + +- warning: +Do not define this property as optional or container will not be able to inject it. +Instead define it with initial value of `Injected()`. +If you need to nilify wrapped value, assing property to `Injected()`. + +**Example**: + +```swift +class ClientImp: Client { + var service = Injected() +} + +``` +- seealso: `InjectedWeak`, `DependencyContainer.resolveDependencies(_:)` + +*/ +public final class Injected: _InjectedPropertyBox { + + static var tag: DependencyContainer.Tag { + return .String("\(Injected.self)") + } + + var _value: Any? + + public var value: T? { + get { + return _value as? T + } + set { + _value = newValue + } + } + + public init() {} + +} + +/** + Use this wrapper to identifiy weak properties of the instance that should be injected when you call + `resolveDependencies()` on this instance. Type T should be a **class** type. + Otherwise it will cause runtime exception when container will try to resolve the property. + Use this wrapper to define one of two circular dependencies to avoid retain cycle. + + - warning: + Do not define this property as optional or container will not be able to inject it. + Instead define it with initial value of `InjectedWeak()`. +If you need to nilify wrapped value, assing property to `InjectedWeak()`. + + **Example**: + + ```swift + class ServiceImp: Service { + var client = InjectedWeak() + } + + ``` + + - note: + The only difference between `InjectedWeak` and `Injected` is that `InjectedWeak` uses _weak_ reference + to store underlying value, when `Injected` uses _strong_ reference. + For that reason if you resolve instance that holds weakly injected property + this property will be released when `resolve` returns 'cause no one else holds reference to it. + + - seealso: `Injected`, `DependencyContainer.resolveDependencies(_:)` + + */ +public final class InjectedWeak: _InjectedWeakPropertyBox { + + //Only classes (means AnyObject) can be used as `weak` properties + //but we can not make cause that will prevent using protocol as generic type + //so we just rely on user reading documentation and passing AnyObject in runtime + //also we will throw fatal error if type can not be casted to AnyObject during resolution + + static var tag: DependencyContainer.Tag { + return .String("\(InjectedWeak.self)") + } + + weak var _value: AnyObject? + + public var value: T? { + get { + return _value as? T + } + set { + _value = newValue as? AnyObject + } + } + + public init() {} + +} + +extension DependencyContainer { + + /** + Resolves dependencies of passed object. Properties that should be injected must be of type `Injected` or `InjectedWeak`. This method will also recursively resolve their dependencies, building full object graph. + + - parameter instance: object whose dependecies should be resolved + + - Note: + Use `InjectedWeak` to define one of two circular dependecies if another dependency is defined as `Injected`. + This will prevent retain cycle between resolved instances. + + **Example**: + ```swift + class ClientImp: Client { + var service = Injected() + } + + class ServiceImp: Service { + var client = InjectedWeak() + } + + //when resolved client will have service injected + let client = container.resolve() as Client + + ``` + + */ + public func resolveDependencies(instance: Any) { + for child in Mirror(reflecting: instance).children { + do { + try (child.value as? _AutoInjectedPropertyBox)?.resolve(self) + } catch { + print(error) + } + } + } + +} + +//MARK: - Private + +typealias InjectedFactory = ()->Any +typealias InjectedWeakFactory = ()->AnyObject + +extension DependencyContainer { + + func registerInjected(definition: AutoInjectedDefinition) { + guard let key = definition.injectedKey, + definition = definition.injectedDefinition else { return } + definitions[key] = definition + } + + func registerInjectedWeak(definition: AutoInjectedDefinition) { + guard let key = definition.injectedWeakKey, + definition = definition.injectedWeakDefinition else { return } + definitions[key] = definition + } + + func removeInjected(definition: AutoInjectedDefinition) { + guard definition.injectedDefinition != nil else { return } + definitions[definition.injectedKey] = nil + } + + func removeInjectedWeak(definition: AutoInjectedDefinition) { + guard definition.injectedWeakDefinition != nil else { return } + definitions[definition.injectedWeakKey] = nil + } + +} + +protocol _AutoInjectedPropertyBox { + func resolve(container: DependencyContainer) throws + static var tag: DependencyContainer.Tag { get } +} + +protocol _InjectedPropertyBox: class, _AutoInjectedPropertyBox { + var _value: Any? { get set } +} + +extension _InjectedPropertyBox { + func resolve(container: DependencyContainer) throws { + self._value = try container.resolve(tag: self.dynamicType.tag) as Any + } +} + +protocol _InjectedWeakPropertyBox: class, _AutoInjectedPropertyBox { + weak var _value: AnyObject? { get set } +} + +extension _InjectedWeakPropertyBox { + func resolve(container: DependencyContainer) throws { + self._value = try container.resolve(tag: self.dynamicType.tag) as AnyObject + } +} + +func isInjectedTag(tag: DependencyContainer.Tag?) -> String? { + guard let tag = tag else { return nil } + guard case let .String(stringTag) = tag else { return nil } + + return try! stringTag.match("^Injected(?:Weak){0,1}<\\((.+)\\)>$")?.first +} + +extension String { + private func match(pattern: String) throws -> [String]? { + let expr = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions()) + let result = expr.firstMatchInString(self, options: NSMatchingOptions(), range: NSMakeRange(0, characters.count)) + if let result = result { + let groups = (1..: Definition { guard resolveDependenciesBlock == nil else { fatalError("You can not change resolveDependencies block after it was set.") } - resolveDependenciesBlock = block + self.resolveDependenciesBlock = block + self.injectedDefinition?.resolveDependenciesBlock = { try block($0, $1 as! T) } + self.injectedWeakDefinition?.resolveDependenciesBlock = { try block($0, $1 as! T) } return self } let factory: F - var scope: ComponentScope - var resolveDependenciesBlock: ((DependencyContainer, T) throws -> ())? + private(set) var scope: ComponentScope = .Prototype - init(factory: F, scope: ComponentScope) { + private(set) var resolveDependenciesBlock: ((DependencyContainer, T) throws -> ())? + + private init(factory: F) { self.factory = factory + } + + public convenience init(scope: ComponentScope, factory: F) { + self.init(factory: factory) self.scope = scope + + if let factory = factory as? ()->T { + injectedDefinition = DefinitionOfAny>(factory: { factory() }) + injectedDefinition!.scope = scope + injectedKey = DefinitionKey(protocolType: Any.self, factoryType: InjectedFactory.self, associatedTag: Injected.tag) + + injectedWeakDefinition = DefinitionOfAnyObject>(factory: { + guard let result = factory() as? AnyObject else { + fatalError("\(T.self) can not be casted to AnyObject. InjectedWeak wrapper should be used to wrap only classes.") + } + return result + }) + injectedWeakDefinition!.scope = scope + injectedWeakKey = DefinitionKey(protocolType: AnyObject.self, factoryType: InjectedWeakFactory.self, associatedTag: InjectedWeak.tag) + } } ///Will be stored only if scope is `Singleton` var resolvedInstance: T? { get { guard scope == .Singleton else { return nil } - return _resolvedInstance + + return _resolvedInstance ?? + injectedDefinition?._resolvedInstance as? T ?? + injectedWeakDefinition?._resolvedInstance as? T } set { guard scope == .Singleton else { return } + _resolvedInstance = newValue + injectedDefinition?._resolvedInstance = newValue + injectedWeakDefinition?._resolvedInstance = newValue as? AnyObject } } private var _resolvedInstance: T? + + ///Accessory definition used to auto-inject strong properties + private(set) var injectedDefinition: DefinitionOfAny>? + private(set) var injectedKey: DefinitionKey? + + ///Accessory definition used to auto-inject weak properties + private(set) var injectedWeakDefinition: DefinitionOfAnyObject>? + private(set) var injectedWeakKey: DefinitionKey? + } ///Dummy protocol to store definitions for different types in collection -protocol Definition: class {} +public protocol Definition: class { } + +protocol AutoInjectedDefinition: Definition { + var injectedDefinition: DefinitionOfAny>? { get } + var injectedKey: DefinitionKey? { get } + + var injectedWeakDefinition: DefinitionOfAnyObject>? { get } + var injectedWeakKey: DefinitionKey? { get } +} + +extension DefinitionOf: AutoInjectedDefinition {} extension DefinitionOf: CustomStringConvertible { public var description: String { diff --git a/Dip/Dip/Dip.swift b/Dip/Dip/Dip.swift index 8d79a65..f3c66df 100644 --- a/Dip/Dip/Dip.swift +++ b/Dip/Dip/Dip.swift @@ -57,27 +57,35 @@ public final class DependencyContainer { configBlock(self) } - // MARK: - Reset all dependencies - - /** - Clear all the previously registered dependencies on this container. - */ - public func reset() { - definitions.removeAll() - } + // MARK: - Removing definitions /** Removes previously registered definition from container. - parameter tag: tag used to register definition - parameter definition: definition to remove - */ + */ public func remove(definition: DefinitionOf, forTag tag: Tag? = nil) { let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) - definitions[key] = nil + remove(definition, forKey: key) } - // MARK: Register dependencies + public func remove(definition: Definition, forKey key: DefinitionKey) { + definitions[key] = nil + if let definition = definition as? AutoInjectedDefinition { + removeInjected(definition) + removeInjectedWeak(definition) + } + } + + /** + Clear all the previously registered dependencies on this container. + */ + public func reset() { + definitions.removeAll() + } + + // MARK: Register definitions /** Register a Void->T factory associated with optional tag. @@ -97,7 +105,7 @@ public final class DependencyContainer { container.register { ClientImp(service: try! container.resolve() as Service) as Client } ``` */ - public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: () throws -> T) -> DefinitionOfT> { + public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: ()->T) -> DefinitionOfT> { return registerFactory(tag: tag, scope: scope, factory: factory) } @@ -123,9 +131,8 @@ public final 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) - definitions[key] = definition + let definition = DefinitionOf(scope: scope, factory: factory) + register(definition, forTag: tag) return definition } @@ -138,13 +145,22 @@ public final class DependencyContainer { */ public func register(definition: DefinitionOf, forTag tag: Tag? = nil) { let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag) - definitions[key] = definition + register(definition, forKey: key) } + public func register(definition: Definition, forKey key: DefinitionKey) { + definitions[key] = definition + + if let definition = definition as? AutoInjectedDefinition where key.associatedTag == nil { + registerInjected(definition) + registerInjectedWeak(definition) + } + } + // MARK: Resolve dependencies /** - Resolve a dependency. + Resolve a dependency. If no definition was registered with this `tag` for this `protocol`, it will try to resolve the definition associated with `nil` (no tag). @@ -174,8 +190,8 @@ public final class DependencyContainer { - 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 - (currently it's up to six) like in this example: + You _should_ use this method only to resolve dependency with more runtime arguments than _Dip_ supports + (currently it's up to six) like in this example: ```swift public func resolve(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, ...) throws -> T { @@ -206,6 +222,7 @@ public final class DependencyContainer { return try resolvedInstances.resolve { if let previouslyResolved: T = resolvedInstances.previouslyResolved(key, definition: definition) { + resolvedInstances.storeResolvedInstance(previouslyResolved, forKey: key, definition: definition) return previouslyResolved } else { @@ -215,16 +232,17 @@ public final class DependencyContainer { //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) { + resolvedInstances.storeResolvedInstance(previouslyResolved, forKey: key, definition: definition) return previouslyResolved } - resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key) + resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key, definition: definition) definition.resolvedInstance = resolvedInstance try definition.resolveDependenciesBlock?(self, resolvedInstance) + resolveDependencies(resolvedInstance) return resolvedInstance } - } } @@ -238,12 +256,16 @@ public final class DependencyContainer { class ResolvedInstances { var resolvedInstances = [DefinitionKey: Any]() - func storeResolvedInstance(instance: T, forKey key: DefinitionKey?) { - self.resolvedInstances[key] = instance + func storeResolvedInstance(instance: T, forKey key: DefinitionKey?, definition: DefinitionOf) { + resolvedInstances[key] = instance + if key != nil { + resolvedInstances[definition.injectedKey] = instance + resolvedInstances[definition.injectedWeakKey] = instance + } } func previouslyResolved(key: DefinitionKey?, definition: DefinitionOf) -> T? { - return (definition.resolvedInstance ?? self.resolvedInstances[key]) as? T + return (definition.resolvedInstance ?? resolvedInstances[key]) as? T } private var depth: Int = 0 @@ -276,18 +298,16 @@ extension DependencyContainer.Tag: IntegerLiteralConvertible { } extension DependencyContainer.Tag: StringLiteralConvertible { - public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType - public typealias UnicodeScalarLiteralType = StringLiteralType - + public init(stringLiteral value: StringLiteralType) { self = .String(value) } - public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) { + public init(unicodeScalarLiteral value: StringLiteralType) { self.init(stringLiteral: value) } - public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) { + public init(extendedGraphemeClusterLiteral value: StringLiteralType) { self.init(stringLiteral: value) } } @@ -309,6 +329,9 @@ public enum DipError: ErrorType, CustomStringConvertible { public var description: String { switch self { case let .DefinitionNotFound(key): + if let wrappedType = isInjectedTag(key.associatedTag) { + return "No definition registered for \(key). Check if you registered factory with no tag and no runtime arguments for type \(wrappedType)." + } return "Failed to resolve type \(key.protocolType) - no definition registered for \(key).\nCheck the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()` and match them with registered factories for type \(key.protocolType)." } } diff --git a/Dip/DipTests/AutoInjectionTests.swift b/Dip/DipTests/AutoInjectionTests.swift new file mode 100644 index 0000000..f02ccc7 --- /dev/null +++ b/Dip/DipTests/AutoInjectionTests.swift @@ -0,0 +1,188 @@ +// +// Dip +// +// 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 +@testable import Dip + +private protocol Server: class { + weak var client: Client? {get} + + var anotherClient: Client? {get set} +} + +private protocol Client: class { + var server: Server? {get} + var anotherServer: Server? {get set} +} + +class AutoInjectionTests: XCTestCase { + + static var serverDeallocated: Bool = false + static var clientDeallocated: Bool = false + + private class ServerImp: Server { + + deinit { + AutoInjectionTests.serverDeallocated = true + } + + var _client = InjectedWeak() + + weak var client: Client? { + return _client.value + } + + weak var anotherClient: Client? + } + + private class ClientImp: Client { + + deinit { + AutoInjectionTests.clientDeallocated = true + } + + var _server = Injected() + var anotherServer: Server? + + var server: Server? { + return _server.value + } + + } + + let container = DependencyContainer() + + override func setUp() { + super.setUp() + + container.reset() + AutoInjectionTests.serverDeallocated = false + AutoInjectionTests.clientDeallocated = false + + container.register(.ObjectGraph) { ServerImp() as Server } + container.register(.ObjectGraph) { ClientImp() as Client } + } + + func testThatItResolvesInjectedDependencies() { + let client = try! container.resolve() as Client + let server = client.server + XCTAssertTrue(client as! ClientImp === server?.client as! ClientImp) + } + + func testThatThereIsNoRetainCycleForCyrcularDependencies() { + //given + var client: Client? = try! container.resolve() as Client + XCTAssertNotNil(client) + + //when + client = nil + + //then + XCTAssertTrue(AutoInjectionTests.clientDeallocated) + XCTAssertTrue(AutoInjectionTests.serverDeallocated) + } + + func testThatItResolvesAutoInjectedSingletons() { + container.reset() + + //given + container.register(.Singleton) { ServerImp() as Server } + container.register(.Singleton) { ClientImp() as Client } + + //when + let sharedClient = try! container.resolve() as Client + let sharedServer = try! container.resolve() as Server + + let client = try! container.resolve() as Client + let server = client.server + + //then + XCTAssertTrue(client as! ClientImp === sharedClient as! ClientImp) + XCTAssertTrue(client as! ClientImp === server?.client as! ClientImp) + XCTAssertTrue(server as! ServerImp === sharedServer as! ServerImp) + } + + func testThatItCallsResolveDependencyBlockOnOriginalDefiniton() { + var serverBlockWasCalled = false + container.register(.ObjectGraph) { ServerImp() as Server } + .resolveDependencies { (container, server) -> () in + serverBlockWasCalled = true + } + + + try! container.resolve() as Client + XCTAssertTrue(serverBlockWasCalled) + + var clientBlockWasCalled = false + container.register(.ObjectGraph) { ClientImp() as Client } + .resolveDependencies { (container, client) -> () in + clientBlockWasCalled = true + } + try! container.resolve() as Server + + XCTAssertTrue(clientBlockWasCalled) + } + + func testThatItReuseResolvedAutoInjectedInstences() { + + container.register(.ObjectGraph) { ServerImp() as Server } + .resolveDependencies { (container, server) -> () in + server.anotherClient = try! container.resolve() as Client + } + + container.register(.ObjectGraph) { ClientImp() as Client } + .resolveDependencies { (container, client) -> () in + client.anotherServer = try! container.resolve() as Server + } + + let client = (try! container.resolve() as Client) as! ClientImp + + let server = client.server as! ServerImp + let anotherServer = client.anotherServer as! ServerImp + + XCTAssertTrue(server === anotherServer) + + let oneClient = server.client as! ClientImp + let anotherClient = server.anotherClient as! ClientImp + + XCTAssertTrue(oneClient === anotherClient) + XCTAssertTrue(client === anotherClient) + } + + func testThatThereIsNoRetainCycleBetweenCircularDependencies() { + var client: Client? = try! container.resolve() as Client + weak var server: Server? = client?.server + + weak var weakClient = client + + XCTAssertNotNil(weakClient) + XCTAssertNotNil(server) + + client = nil + + XCTAssertNil(weakClient) + XCTAssertNil(server) + + } +} diff --git a/Dip/DipTests/ComponentScopeTests.swift b/Dip/DipTests/ComponentScopeTests.swift index 674615e..c15dc83 100644 --- a/Dip/DipTests/ComponentScopeTests.swift +++ b/Dip/DipTests/ComponentScopeTests.swift @@ -31,22 +31,16 @@ class ComponentScopeTests: XCTestCase { 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(ComponentScope.Singleton) { ServiceImp1() as Service } + func testThatScopeCanBeChanged() { + let def = container.register(.Singleton) { ServiceImp1() as Service } XCTAssertEqual(def.scope, ComponentScope.Singleton) } From 345b6e164a28881dc2942bdaa50bc1208b52278c Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Fri, 8 Jan 2016 00:17:43 +0100 Subject: [PATCH 2/7] minor internal protocols refactoring --- Dip/Dip/AutoInjection.swift | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Dip/Dip/AutoInjection.swift b/Dip/Dip/AutoInjection.swift index 8072a00..a5755bd 100644 --- a/Dip/Dip/AutoInjection.swift +++ b/Dip/Dip/AutoInjection.swift @@ -46,10 +46,6 @@ class ClientImp: Client { */ public final class Injected: _InjectedPropertyBox { - static var tag: DependencyContainer.Tag { - return .String("\(Injected.self)") - } - var _value: Any? public var value: T? { @@ -101,10 +97,6 @@ public final class InjectedWeak: _InjectedWeakPropertyBox { //so we just rely on user reading documentation and passing AnyObject in runtime //also we will throw fatal error if type can not be casted to AnyObject during resolution - static var tag: DependencyContainer.Tag { - return .String("\(InjectedWeak.self)") - } - weak var _value: AnyObject? public var value: T? { @@ -150,7 +142,7 @@ extension DependencyContainer { public func resolveDependencies(instance: Any) { for child in Mirror(reflecting: instance).children { do { - try (child.value as? _AutoInjectedPropertyBox)?.resolve(self) + try (child.value as? _AnyInjectedPropertyBox)?.resolve(self) } catch { print(error) } @@ -190,28 +182,38 @@ extension DependencyContainer { } -protocol _AutoInjectedPropertyBox { +protocol _AnyInjectedPropertyBox: class { func resolve(container: DependencyContainer) throws static var tag: DependencyContainer.Tag { get } } -protocol _InjectedPropertyBox: class, _AutoInjectedPropertyBox { +extension _AnyInjectedPropertyBox { + static var tag: DependencyContainer.Tag { + return .String(String(self)) + } + + func _resolve(container: DependencyContainer) throws -> T { + return try container.resolve(tag: self.dynamicType.tag) as T + } +} + +protocol _InjectedPropertyBox: _AnyInjectedPropertyBox { var _value: Any? { get set } } extension _InjectedPropertyBox { func resolve(container: DependencyContainer) throws { - self._value = try container.resolve(tag: self.dynamicType.tag) as Any + self._value = try _resolve(container) as Any } } -protocol _InjectedWeakPropertyBox: class, _AutoInjectedPropertyBox { +protocol _InjectedWeakPropertyBox: _AnyInjectedPropertyBox { weak var _value: AnyObject? { get set } } extension _InjectedWeakPropertyBox { func resolve(container: DependencyContainer) throws { - self._value = try container.resolve(tag: self.dynamicType.tag) as AnyObject + self._value = try _resolve(container) as AnyObject } } From 691762242f6f46eef6179ea29b6dded847dc2214 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sat, 9 Jan 2016 13:14:01 +0100 Subject: [PATCH 3/7] auto injection playground page and updated documentation --- CHANGELOG.md | 5 + Dip/Dip/AutoInjection.swift | 6 +- .../Contents.swift | 183 ++++++++++++++++++ .../timeline.xctimeline | 6 + .../Contents.swift | 2 +- DipPlayground.playground/Sources/Models.swift | 23 +++ .../contents.xcplayground | 1 + README.md | 32 +++ 8 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift create mode 100644 DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/timeline.xctimeline diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c39c9..eb89dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## Develop + +* Added auto-injection feature + [#13](https://github.com/AliSoftware/Dip/pull/13), [@ilyapuchka](https://github.com/ilyapuchka) + ## 4.0.0 #### New Features diff --git a/Dip/Dip/AutoInjection.swift b/Dip/Dip/AutoInjection.swift index a5755bd..3991d21 100644 --- a/Dip/Dip/AutoInjection.swift +++ b/Dip/Dip/AutoInjection.swift @@ -121,7 +121,9 @@ extension DependencyContainer { - Note: Use `InjectedWeak` to define one of two circular dependecies if another dependency is defined as `Injected`. - This will prevent retain cycle between resolved instances. + This will prevent a retain cycle between resolved instances. + + - Warning: If you resolve dependencies of the object created not by container and it has auto-injected circular dependency, container will be not able to resolve it correctly because container does not have this object in it's resolved instances stack. Thus it will create another instance of that type to satisfy circular dependency. **Example**: ```swift @@ -134,7 +136,7 @@ extension DependencyContainer { } //when resolved client will have service injected - let client = container.resolve() as Client + let client = try! container.resolve() as Client ``` diff --git a/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000..c4434e7 --- /dev/null +++ b/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift @@ -0,0 +1,183 @@ +//: [Previous: Shared Instances](@previous) + +import UIKit +import Dip + +let container = DependencyContainer() +/*: + +### Auto-Injection + +If you follow Single Responsibility Principle chances are very high that you will end up with more than two collaborating components in your system. Let's say you have a component that depends on few others. Using _Dip_ you can register all of the dependencies in a container as well as that component itself and register a factory that will create that component and feed it with the dependencies resolving them with a container: +*/ + +protocol Service: class { + var logger: Logger? { get set } + var tracker: Tracker? { get set } +} + +class ServiceImp: Service { + var logger: Logger? + var tracker: Tracker? +} + +container.register() { TrackerImp() as Tracker } +container.register() { LoggerImp() as Logger } + +container.register() { ServiceImp() as Service } + .resolveDependencies { container, service in + service.logger = try! container.resolve() as Logger + service.tracker = try! container.resolve() as Tracker +} + +let service = try! container.resolve() as Service +service.logger +service.tracker + +/*: +Not bad so far. Though that `resolveDependencies` block looks heavy. It would be cool if we can get rid of it. Alternatively you can use _constructor injection_ here, which is actually more prefereable by default but not always possible (see [circular dependencies](Circular%20dependencies)). +Now let's say that you have a bunch of components in your app that require `Logger` or `Tracker` too. You will need to resolve them in a factory for each component again and again. That can be a lot of boilerplate code, simple but still duplicated. + +That is one of the scenarios when auto-injection can be usefull. It works with property injection and with it the previous code will transform to this: +*/ + +class AutoInjectedServiceImp: Service { + private var injectedLogger = Injected() + var logger: Logger? { get { return injectedLogger.value } set { injectedLogger.value = newValue } } + + private var injectedTracker = Injected() + var tracker: Tracker? { get { return injectedTracker.value } set { injectedTracker.value = newValue } } +} + +container.register() { AutoInjectedServiceImp() as Service } + +let autoInjectedService = try! container.resolve() as Service +autoInjectedService.logger +autoInjectedService.tracker + +/*: +The same you can do if you already have an instance of service and just want to resolve its dependencies: +*/ + +let providedService = AutoInjectedServiceImp() +container.resolveDependencies(providedService) +providedService.logger +providedService.tracker + +/*: +As you can see we added two private properties to our implementation of `Service` - `injectedLogger` and `injectedTracker`. Their types are `Injeceted` and `Injected` respectively. Note that we've not just defined them as properties of those types, but defined them with some initial value. `Injected` is a simple _wrapper class_ that wraps value of generic type and provides read-write access to it with `value` property. This property is defined as optional, so that when we create instance of `Injected` it will have `nil` in its value. There is also another wrapper - `InjectedWeak` - which in contrast to `Injected` holds a week reference to its wrapped object, thus requiring it to be a _reference type_ (or `AnyObject`), when `Injected` can also wrap value types (or `Any`). + +What is happening under the hood is that after concrete instance of resolved type is created (`Service` in that case), container will iterate through its properties using `Mirror`. For each of the properties wrapped with `Injected` or `InjectedWeak` it will search a definition that can be used to create an instance of wrapped type and use it to create and inject a concrete instance in a `value` property of a wrapper. The fact that wrappers are _classes_ or _reference types_ makes it possible at runtime to inject dependency in instance of resolved type. + +Another example of using auto-injection is circular dependencies. Let's say you have a `Server` and a `ServerClient` both referencing each other. Standard way to register such components in `DependencyContainer` will lead to such code: +*/ + +protocol Server: class { + weak var client: ServerClient? {get set} +} + +protocol ServerClient: class { + var server: Server? {get} +} + +class ServerImp: Server { + weak var client: ServerClient? +} + +class ServerClientImp: ServerClient { + var server: Server? + + init(server: Server) { + self.server = server + } +} + +container.register(.ObjectGraph) { + ServerClientImp(server: try! container.resolve()) as ServerClient +} + +container.register(.ObjectGraph) { ServerImp() as Server } + .resolveDependencies { container, server in + server.client = try! container.resolve() as ServerClient +} + +let client = try! container.resolve() as ServerClient +client.server + +/*: +With auto-injection you will have the following code: +*/ + +class InjectedServerImp: Server { + private var injectedClient = InjectedWeak() + var client: ServerClient? { get { return injectedClient.value } set { injectedClient.value = newValue }} +} + +class InjectedClientImp: ServerClient { + private var injectedServer = Injected() + var server: Server? { get { return injectedServer.value} } +} + +container.register(.ObjectGraph) { InjectedServerImp() as Server } +container.register(.ObjectGraph) { InjectedClientImp() as ServerClient } + +let injectedClient = try! container.resolve() as ServerClient +injectedClient.server +injectedClient.server?.client === injectedClient //circular dependencies were resolved correctly + +/*: +You can see that component registration looks much simpler now. But on the otherside it requires some boilerplate code in implementations, also tightly coupling them with Dip. + +There is one more use case when auto-injection can be very helpfull - when you don't create instances by yourself but system creates them for you. It can be view controllers created by Storyboards. Let's say each view controller in your application requires logger, tracker, data layer service, router, etc. You can end up with code like this: +*/ +container.register() { RouterImp() as Router } +container.register() { DataProviderImp() as DataProvider } + +class ViewController: UIViewController { + var logger: Logger? + var tracker: Tracker? + var dataProvider: DataProvider? + var router: Router? + + //it's better not to access container directly in implementation but that's ok for illustration + func injectDependencies(container: DependencyContainer) { + logger = try! container.resolve() as Logger + tracker = try! container.resolve() as Tracker + dataProvider = try! container.resolve() as DataProvider + router = try! container.resolve() as Router + } +} + +let viewController = ViewController() +viewController.injectDependencies(container) +viewController.router + +/*: +With auto-injection you can replace that with something like this: +*/ + +class AutoInjectedViewController: UIViewController { + + var logger = Injected() + var tracker = Injected() + var dataProvider = Injected() + var router = Injected() + + func injectDependencies(container: DependencyContainer) { + container.resolveDependencies(self) + } +} + +let autoViewController = AutoInjectedViewController() +autoViewController.injectDependencies(container) +autoViewController.router.value + +/*: +In such scenario you will need to use property injection anyway, so the overhead of adding additional properties for auto-injection is smaller. Also all the boilerplate code of unwrapping injected properties (if you need that) can be moved to extension, cleaning implementation a bit. + +So as you can see there are certain advantages and disadvatages of using auto-injection. It makes your definitions simpler, especially if there are circular dependencies involved, and lets you get rid of giant constructors overloaded with arguments. But it requires additional properties and some boilerplate code in your implementations, makes your implementatios tightly coupled with Dip. It has also some limitations like that it requires factories for auto-injected types that accept no runtime arguments and have no associated tags to be registered in a container. + +So you should decide for yourself whether you prefer to use auto-injection or "the standard" way. At the end they let you achieve the same result. +*/ + +//: [Next: Testing](@next) diff --git a/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/timeline.xctimeline b/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/timeline.xctimeline new file mode 100644 index 0000000..bf468af --- /dev/null +++ b/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/timeline.xctimeline @@ -0,0 +1,6 @@ + + + + + diff --git a/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift index 0deee4f..b7f17c9 100644 --- a/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift @@ -158,6 +158,6 @@ If you want to know more about Dependency Injection in general we recomend you t */ -//: [Next: Testing](@next) +//: [Next: Auto-Injection](@next) diff --git a/DipPlayground.playground/Sources/Models.swift b/DipPlayground.playground/Sources/Models.swift index 6182743..fbc7e18 100644 --- a/DipPlayground.playground/Sources/Models.swift +++ b/DipPlayground.playground/Sources/Models.swift @@ -53,3 +53,26 @@ public class ClientServiceImp: Service { public init() {} } +public protocol Logger {} +public protocol Tracker {} +public protocol DataProvider {} +public protocol Router {} + +public class LoggerImp: Logger { + public init() {} +} + +public class TrackerImp: Tracker { + public init() {} +} + +public class RouterImp: Router { + public init() {} +} + +public class DataProviderImp: DataProvider { + public init() {} +} + + + diff --git a/DipPlayground.playground/contents.xcplayground b/DipPlayground.playground/contents.xcplayground index c8e37c5..948f5fe 100644 --- a/DipPlayground.playground/contents.xcplayground +++ b/DipPlayground.playground/contents.xcplayground @@ -9,6 +9,7 @@ + \ No newline at end of file diff --git a/README.md b/README.md index 756152f..4a93594 100644 --- a/README.md +++ b/README.md @@ -181,12 +181,44 @@ container.register(.ObjectGraph) { ServerImp() as Server } ``` More infromation about circular dependencies you can find in a playground. +### Auto-Injections + +Auto-injection lets your resolve all the dependencies of the instance (created manually or resolved by container) with just one call to `resolve`, also allowing a simpler sintax to register circular dependencies. + +```swift +protocol Server { + weak var client: Client? { get } +} + +protocol Client: class { + var server: Server? { get } +} + +class ServerImp: Server { + private var injectedClient = InjectedWeak() + var client: Client? { return injectedClient.value } +} + +class ClientImp: Client { + private var injectedServer = Injected() + var server: Server? { get { return injectedServer.value} } +} + +container.register(.ObjectGraph) { ServerImp() as Server } +container.register(.ObjectGraph) { ClientImp() as Client } + +let client = try! container.resolve() as Client + +``` +You can find more use cases for auto-injection in a Playground. + ### 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. ### Errors + The resolve operation is potentially dangerous because you can use the wrong type, factory or a wrong tag. For that reason Dip throws an error `DefinitionNotFond(DefinitionKey)` if it failed to resolve type. When calling `resolve` you need to use a `try` operator. There are rare use cases where your application can recover from this kind of errors (for example you can register new types From 33601418f372af1ce4d1dd0f95aa98c7bb44030b Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sun, 10 Jan 2016 16:08:32 +0100 Subject: [PATCH 4/7] added throws to auto injection factories --- Dip/Dip/AutoInjection.swift | 4 ++-- Dip/Dip/Definition.swift | 16 ++++++++-------- Dip/Dip/Dip.swift | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Dip/Dip/AutoInjection.swift b/Dip/Dip/AutoInjection.swift index 3991d21..61d1a63 100644 --- a/Dip/Dip/AutoInjection.swift +++ b/Dip/Dip/AutoInjection.swift @@ -155,8 +155,8 @@ extension DependencyContainer { //MARK: - Private -typealias InjectedFactory = ()->Any -typealias InjectedWeakFactory = ()->AnyObject +typealias InjectedFactory = () throws -> Any +typealias InjectedWeakFactory = () throws -> AnyObject extension DependencyContainer { diff --git a/Dip/Dip/Definition.swift b/Dip/Dip/Definition.swift index b6ffabd..c9b2f9a 100644 --- a/Dip/Dip/Definition.swift +++ b/Dip/Dip/Definition.swift @@ -115,13 +115,13 @@ public final class DefinitionOf: Definition { self.init(factory: factory) self.scope = scope - if let factory = factory as? ()->T { - injectedDefinition = DefinitionOfAny>(factory: { factory() }) + if let factory = factory as? () throws -> T { + injectedDefinition = DefinitionOf(factory: { try factory() }) injectedDefinition!.scope = scope injectedKey = DefinitionKey(protocolType: Any.self, factoryType: InjectedFactory.self, associatedTag: Injected.tag) - injectedWeakDefinition = DefinitionOfAnyObject>(factory: { - guard let result = factory() as? AnyObject else { + injectedWeakDefinition = DefinitionOf(factory: { + guard let result = try factory() as? AnyObject else { fatalError("\(T.self) can not be casted to AnyObject. InjectedWeak wrapper should be used to wrap only classes.") } return result @@ -152,11 +152,11 @@ public final class DefinitionOf: Definition { private var _resolvedInstance: T? ///Accessory definition used to auto-inject strong properties - private(set) var injectedDefinition: DefinitionOfAny>? + private(set) var injectedDefinition: DefinitionOf? private(set) var injectedKey: DefinitionKey? ///Accessory definition used to auto-inject weak properties - private(set) var injectedWeakDefinition: DefinitionOfAnyObject>? + private(set) var injectedWeakDefinition: DefinitionOf? private(set) var injectedWeakKey: DefinitionKey? } @@ -165,10 +165,10 @@ public final class DefinitionOf: Definition { public protocol Definition: class { } protocol AutoInjectedDefinition: Definition { - var injectedDefinition: DefinitionOfAny>? { get } + var injectedDefinition: DefinitionOf? { get } var injectedKey: DefinitionKey? { get } - var injectedWeakDefinition: DefinitionOfAnyObject>? { get } + var injectedWeakDefinition: DefinitionOf? { get } var injectedWeakKey: DefinitionKey? { get } } diff --git a/Dip/Dip/Dip.swift b/Dip/Dip/Dip.swift index f3c66df..e82758e 100644 --- a/Dip/Dip/Dip.swift +++ b/Dip/Dip/Dip.swift @@ -105,7 +105,7 @@ public final class DependencyContainer { container.register { ClientImp(service: try! container.resolve() as Service) as Client } ``` */ - public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: ()->T) -> DefinitionOfT> { + public func register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: () throws -> T) -> DefinitionOfT > { return registerFactory(tag: tag, scope: scope, factory: factory) } From 6f5ab994a0632f2b2dfd2633e2927c432aadef07 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sun, 10 Jan 2016 15:49:43 +0100 Subject: [PATCH 5/7] fixed typos --- .../Pages/Auto-injection.xcplaygroundpage/Contents.swift | 4 ++-- README.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift index c4434e7..3f19965 100644 --- a/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift @@ -38,7 +38,7 @@ service.tracker Not bad so far. Though that `resolveDependencies` block looks heavy. It would be cool if we can get rid of it. Alternatively you can use _constructor injection_ here, which is actually more prefereable by default but not always possible (see [circular dependencies](Circular%20dependencies)). Now let's say that you have a bunch of components in your app that require `Logger` or `Tracker` too. You will need to resolve them in a factory for each component again and again. That can be a lot of boilerplate code, simple but still duplicated. -That is one of the scenarios when auto-injection can be usefull. It works with property injection and with it the previous code will transform to this: +That is one of the scenarios when auto-injection can be useful. It works with property injection and with it the previous code will transform to this: */ class AutoInjectedServiceImp: Service { @@ -128,7 +128,7 @@ injectedClient.server?.client === injectedClient //circular dependencies were re /*: You can see that component registration looks much simpler now. But on the otherside it requires some boilerplate code in implementations, also tightly coupling them with Dip. -There is one more use case when auto-injection can be very helpfull - when you don't create instances by yourself but system creates them for you. It can be view controllers created by Storyboards. Let's say each view controller in your application requires logger, tracker, data layer service, router, etc. You can end up with code like this: +There is one more use case when auto-injection can be very helpful - when you don't create instances by yourself but system creates them for you. It can be view controllers created by Storyboards. Let's say each view controller in your application requires logger, tracker, data layer service, router, etc. You can end up with code like this: */ container.register() { RouterImp() as Router } container.register() { DataProviderImp() as DataProvider } diff --git a/README.md b/README.md index 4a93594..6d382e1 100644 --- a/README.md +++ b/README.md @@ -181,9 +181,9 @@ container.register(.ObjectGraph) { ServerImp() as Server } ``` More infromation about circular dependencies you can find in a playground. -### Auto-Injections +### Auto-Injection -Auto-injection lets your resolve all the dependencies of the instance (created manually or resolved by container) with just one call to `resolve`, also allowing a simpler sintax to register circular dependencies. +Auto-injection lets your resolve all the dependencies of the instance (created manually or resolved by container) with just one call to `resolve`, also allowing a simpler syntax to register circular dependencies. ```swift protocol Server { @@ -210,7 +210,7 @@ container.register(.ObjectGraph) { ClientImp() as Client } let client = try! container.resolve() as Client ``` -You can find more use cases for auto-injection in a Playground. +You can find more use cases for auto-injection in the Playground available in this repository. ### Thread safety From d50ca57d493c677f35d39bcb91b919fc009a8e25 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sun, 10 Jan 2016 16:42:21 +0100 Subject: [PATCH 6/7] updated CHANGELOG --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb89dcc..f71207e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,10 @@ ## Develop -* Added auto-injection feature +* Added auto-injection feature. [#13](https://github.com/AliSoftware/Dip/pull/13), [@ilyapuchka](https://github.com/ilyapuchka) +* Factories and `resolveDependencies` blocks of `DefinitionOf` are now allowed to `throw`. + [#32](https://github.com/AliSoftware/Dip/pull/13), [@ilyapuchka](https://github.com/ilyapuchka) ## 4.0.0 From cc399031e7d5e8586c6dcac10d11fef3d30d0b4a Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sun, 10 Jan 2016 19:48:26 +0100 Subject: [PATCH 7/7] removed forced try in playground --- .../Pages/Auto-injection.xcplaygroundpage/Contents.swift | 8 ++++---- .../Runtime arguments.xcplaygroundpage/Contents.swift | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift index 3f19965..5f8f3f7 100644 --- a/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift @@ -26,8 +26,8 @@ container.register() { LoggerImp() as Logger } container.register() { ServiceImp() as Service } .resolveDependencies { container, service in - service.logger = try! container.resolve() as Logger - service.tracker = try! container.resolve() as Tracker + service.logger = try container.resolve() as Logger + service.tracker = try container.resolve() as Tracker } let service = try! container.resolve() as Service @@ -93,12 +93,12 @@ class ServerClientImp: ServerClient { } container.register(.ObjectGraph) { - ServerClientImp(server: try! container.resolve()) as ServerClient + ServerClientImp(server: try container.resolve()) as ServerClient } container.register(.ObjectGraph) { ServerImp() as Server } .resolveDependencies { container, server in - server.client = try! container.resolve() as ServerClient + server.client = try container.resolve() as ServerClient } let client = try! container.resolve() as ServerClient diff --git a/DipPlayground.playground/Pages/Runtime arguments.xcplaygroundpage/Contents.swift b/DipPlayground.playground/Pages/Runtime arguments.xcplaygroundpage/Contents.swift index 2bb08a0..05a1d99 100644 --- a/DipPlayground.playground/Pages/Runtime arguments.xcplaygroundpage/Contents.swift +++ b/DipPlayground.playground/Pages/Runtime arguments.xcplaygroundpage/Contents.swift @@ -35,12 +35,12 @@ _Dip_ supports up to six runtime arguments. If that is not enougth you can exten */ extension DependencyContainer { - 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 register(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) throws -> T) -> DefinitionOf T> { + return registerFactory(tag: tag, scope: scope, factory: factory) } - public func resolve(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) throws -> T { - return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6) } + public func resolve(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) throws -> T { + return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5, arg6, arg7) } } }