Skip to content

Commit

Permalink
auto-injection
Browse files Browse the repository at this point in the history
  • Loading branch information
Ilya Puchka authored and ilyapuchka committed Dec 13, 2015
1 parent c6bf181 commit 9a99ed4
Show file tree
Hide file tree
Showing 6 changed files with 550 additions and 43 deletions.
18 changes: 18 additions & 0 deletions Dip/Dip.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -82,6 +89,8 @@
0919F4D01C16417000DC3B10 /* DipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DipTests.swift; sourceTree = "<group>"; };
0919F4D11C16417000DC3B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeArgumentsTests.swift; sourceTree = "<group>"; };
09873F551C1E0237000C02F6 /* AutoInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInjection.swift; sourceTree = "<group>"; };
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInjectionTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -147,6 +156,7 @@
0919F4CA1C16417000DC3B10 /* Dip.swift */,
0919F4C81C16417000DC3B10 /* Definition.swift */,
0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */,
09873F551C1E0237000C02F6 /* AutoInjection.swift */,
0919F4CB1C16417000DC3B10 /* Info.plist */,
);
path = Dip;
Expand All @@ -159,6 +169,7 @@
0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */,
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */,
0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */,
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */,
0919F4D11C16417000DC3B10 /* Info.plist */,
);
path = DipTests;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
};
Expand All @@ -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;
Expand All @@ -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;
};
Expand All @@ -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;
Expand All @@ -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;
};
Expand All @@ -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;
Expand Down
241 changes: 241 additions & 0 deletions Dip/Dip/AutoInjection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
//
// 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>: _InjectedPropertyBox {

static var tag: DependencyContainer.Tag {
return .String("\(Injected<T>.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<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>: _InjectedWeakPropertyBox {

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

static var tag: DependencyContainer.Tag {
return .String("\(InjectedWeak<T>.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<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 {
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..<result.numberOfRanges).map {
(self as NSString).substringWithRange(result.rangeAtIndex($0))
}
return groups
}
else {
return nil
}
}
}


Loading

0 comments on commit 9a99ed4

Please sign in to comment.