Skip to content

Commit

Permalink
reverted DefinitionOf to final class, added auto-injection implementa…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
Ilya Puchka authored and ilyapuchka committed Nov 22, 2015
1 parent 0c93e86 commit c3a7afb
Show file tree
Hide file tree
Showing 7 changed files with 438 additions and 52 deletions.
8 changes: 8 additions & 0 deletions Dip/Dip.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
097EE5B21BED394B00A358BB /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097EE5B11BED394B00A358BB /* AutoInjection.swift */; };
097EE5B41BED4EFE00A358BB /* AutoInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097EE5B31BED4EFE00A358BB /* AutoInjectionTests.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 */
Expand All @@ -39,6 +41,8 @@
094526B31BEA51540034E72A /* RuntimeArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = RuntimeArguments.swift; sourceTree = "<group>"; tabWidth = 2; };
094526B51BEA520B0034E72A /* Definition.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Definition.swift; sourceTree = "<group>"; tabWidth = 2; };
094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeArgumentsTests.swift; sourceTree = "<group>"; };
097EE5B11BED394B00A358BB /* AutoInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInjection.swift; sourceTree = "<group>"; };
097EE5B31BED4EFE00A358BB /* AutoInjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInjectionTests.swift; sourceTree = "<group>"; };
0989323E1BEBC8CD00ACDA2B /* ComponentScopeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComponentScopeTests.swift; sourceTree = "<group>"; };
09969C541BEB7C0A00F93C70 /* Dip.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Dip.podspec; path = ../Dip.podspec; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -89,6 +93,7 @@
094526AB1BEA1D200034E72A /* Dip.swift */,
094526B31BEA51540034E72A /* RuntimeArguments.swift */,
094526B51BEA520B0034E72A /* Definition.swift */,
097EE5B11BED394B00A358BB /* AutoInjection.swift */,
094526961BEA1CFF0034E72A /* Info.plist */,
);
path = Dip;
Expand All @@ -100,6 +105,7 @@
094526A01BEA1CFF0034E72A /* DipTests.swift */,
094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */,
0989323E1BEBC8CD00ACDA2B /* ComponentScopeTests.swift */,
097EE5B31BED4EFE00A358BB /* AutoInjectionTests.swift */,
094526A21BEA1CFF0034E72A /* Info.plist */,
);
path = DipTests;
Expand Down Expand Up @@ -215,6 +221,7 @@
files = (
094526AC1BEA1D200034E72A /* Dip.swift in Sources */,
094526B61BEA520B0034E72A /* Definition.swift in Sources */,
097EE5B21BED394B00A358BB /* AutoInjection.swift in Sources */,
094526B41BEA51540034E72A /* RuntimeArguments.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -225,6 +232,7 @@
files = (
094526A11BEA1CFF0034E72A /* DipTests.swift in Sources */,
0989323F1BEBC8CD00ACDA2B /* ComponentScopeTests.swift in Sources */,
097EE5B41BED4EFE00A358BB /* AutoInjectionTests.swift in Sources */,
094526B81BEA536A0034E72A /* RuntimeArgumentsTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
213 changes: 213 additions & 0 deletions Dip/Dip/AutoInjection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <[email protected]>
//
// 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<T>()`.
If you need to nilify wrapped value, assing property to `Injected<T>()`.

**Example**:

```swift
class ClientImp: Client {
var service = Injected<Service>()
}

```
- seealso: `InjectedWeak`, `DependencyContainer.resolveDependencies(_:)`

*/
public final class Injected<T>: _Injected {

var _value: Any?

public init(_ value: T? = nil) {
self._value = value
}

public var value: T? {
get {
return _value as? T
}
set {
_value = newValue
}
}

}

/**
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<T>()`.
If you need to nilify wrapped value, assing property to `InjectedWeak<T>()`.

**Example**:

```swift
class ServiceImp: Service {
var client = InjectedWeak<Client>()
}

```

- 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<T>: _InjectedWeak {

//Only classes (means AnyObject) can be used as `weak` properties
//but we can not make <T: AnyObject> 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

weak var _value: AnyObject?

public init(_ value: T? = nil) {
self._value = value as? AnyObject
}

public var value: T? {
get {
return _value as? T
}
set {
_value = newValue as? AnyObject
}
}

}

extension DependencyContainer {

/**
Resolves dependencies of passed object. Properties that should be injected must be of type `Injected<T>` or `InjectedWeak<T>`. This method will also recursively resolve their dependencies, building full object graph.

- parameter instance: object whose dependecies should be resolved

- Note:
Use `InjectedWeak<T>` to define one of two circular dependecies if another dependency is defined as `Injected<U>`.
This will prevent retain cycle between resolved instances.

**Example**:
```swift
class ClientImp: Client {
var service = Injected<Service>()
}

class ServiceImp: Service {
var client = InjectedWeak<Client>()
}

//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
where child.value is _Injected || child.value is _InjectedWeak {

let tag = Tag.String("\(child.value.dynamicType)")
if let value = child.value as? _Injected {
value._value = resolve(tag: tag) as Any
}
else if let weakValue = child.value as? _InjectedWeak {
weakValue._value = resolve(tag: tag) as AnyObject
}
}
}

}

//MARK: - Private

extension DependencyContainer {
private typealias InjectedFactory = ()->Any
private typealias InjectedWeakFactory = ()->AnyObject

private func injectedKey(T: Any.Type) -> DefinitionKey {
return DefinitionKey(protocolType: Any.self, factoryType: InjectedFactory.self, associatedTag: Tag.String(injectedTag(T.self)))
}

private func injectedWeakKey(T: Any.Type) -> DefinitionKey {
return DefinitionKey(protocolType: AnyObject.self, factoryType: InjectedWeakFactory.self, associatedTag: Tag.String(injectedWeakTag(T.self)))
}

func registerInjected<T, F>(definition: DefinitionOf<T, F>) {
guard let definition = definition.injectedDefinition else { return }
definitions[injectedKey(T.self)] = definition
}

func registerInjectedWeak<T, F>(definition: DefinitionOf<T, F>) {
guard let definition = definition.injectedWeakDefinition else { return }
definitions[injectedWeakKey(T.self)] = definition
}

func removeInjected<T, F>(definition: DefinitionOf<T, F>) {
guard definition.injectedDefinition != nil else { return }
definitions[injectedKey(T.self)] = nil
}

func removeInjectedWeak<T, F>(definition: DefinitionOf<T, F>) {
guard definition.injectedWeakDefinition != nil else { return }
definitions[injectedWeakKey(T.self)] = nil
}

}

func injectedTag(T: Any.Type) -> String {
return "Injected<\(T.self)>"
}

func injectedWeakTag(T: Any.Type) -> String {
return "InjectedWeak<\(T.self)>"
}

protocol _Injected: class {
var _value: Any? { get set }
}

protocol _InjectedWeak: class {
weak var _value: AnyObject? { get set }
}

47 changes: 33 additions & 14 deletions Dip/Dip/Definition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
// THE SOFTWARE.
//

import Foundation

///Internal representation of a key used to associate definitons and factories by tag, type and factory.
struct DefinitionKey : Hashable, Equatable, CustomDebugStringConvertible {
var protocolType: Any.Type
Expand Down Expand Up @@ -64,7 +62,7 @@ public enum ComponentScope {

For example `DefinitionOf<Service,(String)->Service>` is the type of definition that during resolution will produce instance of type `Service` using closure that accepts `String` argument.
*/
public struct DefinitionOf<T, F>: Definition {
public final class DefinitionOf<T, F>: Definition {

/**
Sets the block that will be used to resolve dependencies of the component.
Expand Down Expand Up @@ -92,10 +90,8 @@ public struct DefinitionOf<T, F>: Definition {
guard resolveDependenciesBlock == nil else {
fatalError("You can not change resolveDependencies block after it was set.")
}
var newDefinition = self
newDefinition.resolveDependenciesBlock = block
container.register(newDefinition)
return newDefinition
self.resolveDependenciesBlock = block
return self
}

let factory: F
Expand All @@ -107,23 +103,46 @@ public struct DefinitionOf<T, F>: Definition {
self.factory = factory
self.scope = scope
self.tag = tag

if let factory = factory as? ()->T where tag == nil {
injectedDefinition = DefinitionOf<Any, ()->Any>(factory: { factory() }, scope: scope, tag: DependencyContainer.Tag.String(injectedTag(T.self)))

injectedWeakDefinition = DefinitionOf<AnyObject, ()->AnyObject>(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
}, scope: scope, tag: DependencyContainer.Tag.String(injectedWeakTag(T.self)))
}

}

///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
}
}

mutating func resolvedInstance(container: DependencyContainer, tag: DependencyContainer.Tag? = nil, instance: T) {
guard scope == .Singleton else { return }
_resolvedInstance = instance
container.register(self)
}

private var _resolvedInstance: T?

///Accessory definition used to auto-inject strong properties
var injectedDefinition: DefinitionOf<Any,()->Any>?

///Accessory definition used to auto-inject weak properties
var injectedWeakDefinition: DefinitionOf<AnyObject,()->AnyObject>?

}

///Dummy protocol to store definitions for different types in collection
Expand Down
Loading

0 comments on commit c3a7afb

Please sign in to comment.