Skip to content

Auto-injection #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG

## Develop

* 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

#### New Features
Expand Down
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
245 changes: 245 additions & 0 deletions Dip/Dip/AutoInjection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
//
// 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 {

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

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 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
class ClientImp: Client {
var service = Injected<Service>()
}

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

//when resolved client will have service injected
let client = try! container.resolve() as Client

```

*/
public func resolveDependencies(instance: Any) {
for child in Mirror(reflecting: instance).children {
do {
try (child.value as? _AnyInjectedPropertyBox)?.resolve(self)
} catch {
print(error)
}
}
}

}

//MARK: - Private

typealias InjectedFactory = () throws -> Any
typealias InjectedWeakFactory = () throws -> 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 _AnyInjectedPropertyBox: class {
func resolve(container: DependencyContainer) throws
static var tag: DependencyContainer.Tag { get }
}

extension _AnyInjectedPropertyBox {
static var tag: DependencyContainer.Tag {
return .String(String(self))
}

func _resolve<T>(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 _resolve(container) as Any
}
}

protocol _InjectedWeakPropertyBox: _AnyInjectedPropertyBox {
weak var _value: AnyObject? { get set }
}

extension _InjectedWeakPropertyBox {
func resolve(container: DependencyContainer) throws {
self._value = try _resolve(container) 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