Skip to content

Commit

Permalink
auto injection playground page and updated documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyapuchka committed Jan 10, 2016
1 parent 099e0f6 commit 932b27c
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 5 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 4 additions & 2 deletions Dip/Dip/AutoInjection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ extension DependencyContainer {
- 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.
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
Expand All @@ -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
```
Expand Down
Original file line number Diff line number Diff line change
@@ -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:

This comment has been minimized.

Copy link
@AliSoftware

AliSoftware Jan 10, 2016

Owner

useful

*/

class AutoInjectedServiceImp: Service {
private var injectedLogger = Injected<Logger>()
var logger: Logger? { get { return injectedLogger.value } set { injectedLogger.value = newValue } }

private var injectedTracker = Injected<Tracker>()
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<Logger>` and `Injected<Tracker>` respectively. Note that we've not just defined them as properties of those types, but defined them with some initial value. `Injected<T>` 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<T>` it will have `nil` in its value. There is also another wrapper - `InjectedWeak<T>` - which in contrast to `Injected<T>` holds a week reference to its wrapped object, thus requiring it to be a _reference type_ (or `AnyObject`), when `Injected<T>` 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<T>` or `InjectedWeak<T>` 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<ServerClient>()
var client: ServerClient? { get { return injectedClient.value } set { injectedClient.value = newValue }}
}

class InjectedClientImp: ServerClient {
private var injectedServer = Injected<Server>()
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:

This comment has been minimized.

Copy link
@AliSoftware

AliSoftware Jan 10, 2016

Owner

helpful

*/
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<Logger>()
var tracker = Injected<Tracker>()
var dataProvider = Injected<DataProvider>()
var router = Injected<Router>()

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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,10 @@ dipController = DipViewController(apiClientProvider: container)
/*:
This way you also does not depend directly on Dip. Instead you provide a boundary between Dip — that you don't have control of — and your source code. So when something chagnes in Dip, you update only the boundary code.
Dependency injection is a pattern as well as singleton. And any pattern can be abused. DI can be use in a [wrong way]((http://www.loosecouplings.com/2011/01/dependency-injection-using-di-container.html)), container can easily become a [service locator](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). That's why if you adopt DI in one part of your system it does not mean that you should inject everything and everywhere. The same with using protocols instead of concrete implementations. For every tool there is a right time and the same way as singleton can harm you the same way DI and protocols abuse can make your code unnececerry complex.
Dependency injection is a pattern as well as singleton. And any pattern can be abused. DI can be used in a [wrong way]((http://www.loosecouplings.com/2011/01/dependency-injection-using-di-container.html)), container can easily become a [service locator](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). That's why if you adopt DI in one part of your system it does not mean that you should inject everything and everywhere. The same with using protocols instead of concrete implementations. For every tool there is a right time and the same way as singleton can harm you the same way DI and protocols abuse can make your code unnececerry complex.
*/

//: [Next: Testing](@next)
//: [Next: Auto-Injection](@next)


23 changes: 23 additions & 0 deletions DipPlayground.playground/Sources/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
}



3 changes: 2 additions & 1 deletion DipPlayground.playground/contents.xcplayground
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='raw'>
<playground version='6.0' target-platform='ios' display-mode='rendered'>
<pages>
<page name='What is Dip?'/>
<page name='Creating container'/>
Expand All @@ -9,6 +9,7 @@
<page name='Scopes'/>
<page name='Circular dependencies'/>
<page name='Shared Instances'/>
<page name='Auto-injection'/>
<page name='Testing'/>
</pages>
</playground>
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,44 @@ container.register(.ObjectGraph) { ServerImp() as Server }
```
More infromation about circular dependencies you can find in a playground.

### Auto-Injections

This comment has been minimized.

Copy link
@AliSoftware

AliSoftware Jan 10, 2016

Owner

I think this title should be singular.


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.

This comment has been minimized.

Copy link
@AliSoftware

AliSoftware Jan 10, 2016

Owner

syntax


```swift
protocol Server {
weak var client: Client? { get }
}

protocol Client: class {
var server: Server? { get }
}

class ServerImp: Server {
private var injectedClient = InjectedWeak<Client>()
var client: Client? { return injectedClient.value }
}

class ClientImp: Client {
private var injectedServer = Injected<Server>()
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.

This comment has been minimized.

Copy link
@AliSoftware

AliSoftware Jan 10, 2016

Owner

in the Playground available in this repository.


### 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
Expand Down

0 comments on commit 932b27c

Please sign in to comment.