Skip to content

Commit

Permalink
updated playground and added page about circular dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyapuchka committed Nov 21, 2015
1 parent 151b117 commit f3368ee
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//: [Previous: Scopes](@previous)

import Dip

let container = DependencyContainer()

/*:
### Circular Dependencies

Very often we encounter situations when we have circular dependencies between components. The most obvious example is delegation pattern. Dip can resolve such dependencies easily.

Let's say you have some network client and it's delegate defined like this:
*/

protocol NetworkClientDelegate: class {
var networkClient: NetworkClient { get }
}

protocol NetworkClient: class {
weak var delegate: NetworkClientDelegate? {get set}
}

class NetworkClientImp: NetworkClient {
weak var delegate: NetworkClientDelegate?
init() {}
}

class Interactor: NetworkClientDelegate {
let networkClient: NetworkClient
init(networkClient: NetworkClient) {
self.networkClient = networkClient
}
}

/*:
Note that one of this classes uses _property injection_ (`NetworkClientImp`) and another uses _constructor injection_ (`Interactor`).
It's very important that _at least one_ of them uses property injection, 'cause if you try to use constructor injection for both of them then you will enter infinite loop when you will call `resolve`.

Now you can register those classes in container:
*/

container.register(.ObjectGraph) { [unowned container] in
Interactor(networkClient: container.resolve()) as NetworkClientDelegate
}

container.register(.ObjectGraph) { NetworkClientImp() as NetworkClient }
.resolveDependencies(container) { (container, client) -> () in
client.delegate = container.resolve() as NetworkClientDelegate
}

/*:
Here you can spot the difference in the way we register classes. `Interactor` class uses constructor injection so to regiter it we use block factory where we call `resolve` to obtain instance of `NetworkClient` and pass it to constructor. `NetworkClientImp` uses property injection for it's delegate property. Again we use block factory to create instance, but to inject delegate property we use special `resolveDependencies` method. Block passed to this method will be called right _after_ block factory. So you can use this block to perform additional setup or, like in this example, to resolve circular dependencies. This way `DependencyContainer` breaks infinite recursion that would happen if we used constructor injection for both of our components.

*Note*: Capturing container as `unowned` reference is important to avoid retain cycle between container and definition.

Now when you resolve `NetworkClientDelegate` you will get instance of `Interactor` that will have client with delegate referencing the same `Interactor` instance:
*/

let interactor = container.resolve() as NetworkClientDelegate
interactor.networkClient.delegate === interactor

/*:
**Warning**: Note that one of the properties (`delegate`) is defined as _weak_. That's crucial to avoid retain cycle. But now if you try to resolve `NetworkClient` first it's delegate will be released before `resolve` returns, 'cuase no one holds a reference to it except the container.
*/

let networkClient = container.resolve() as NetworkClient
networkClient.delegate // delegate was alread released =(

/*:
Note also that we used `.ObjectGraph` scope to register implementations. This is also very important to preserve consistency of objects relationships. If we would have used `.Prototype` scope for both components then container would not reuse instances and we would have infinite loop. Each attemp to resolve `NetworkClientDelegate` will create new instance of `Interactor`. It will resolve `NetworkClient` which will create new instance of `NetworkClientImp`. It will try to resolve it's delegate property and that will create new instance of `Interactor`. And so on and so on. If we would have used `.Prototype` for one of the components it will lead to the same infinite loop or one of the relationships will be invalid:
*/

container.reset()

container.register(.Prototype) { [unowned container] in
Interactor(networkClient: container.resolve()) as NetworkClientDelegate
}

container.register(.ObjectGraph) { NetworkClientImp() as NetworkClient }
.resolveDependencies(container) { (container, client) -> () in
client.delegate = container.resolve() as NetworkClientDelegate
}

let invalidInteractor = container.resolve() as NetworkClientDelegate
invalidInteractor.networkClient.delegate // that is not valid

//: [Next: Shared Instances](@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 @@ -12,21 +12,21 @@ Dip lets you use runtime arguments to register and resolve your components.
Note that __types__, __number__ and __order__ of arguments matters and you can register different factories with different set of runtime arguments for the same protocol. To resolve using one of this factory you will need to pass runtime arguments of the same types, number and in the same order to `resolve` as you used in `register` method.
*/

container.register { (url: NSURL, port: Int) in ServiceImp3(name: "1", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL) in ServiceImp3(name: "2", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL?) in ServiceImp3(name: "3", baseURL: url!, port: port) as Service }
container.register { (port: Int, url: NSURL!) in ServiceImp3(name: "4", baseURL: url, port: port) as Service }
container.register { (url: NSURL, port: Int) in ServiceImp4(name: "1", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL) in ServiceImp4(name: "2", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL?) in ServiceImp4(name: "3", baseURL: url!, port: port) as Service }
container.register { (port: Int, url: NSURL!) in ServiceImp4(name: "4", baseURL: url, port: port) as Service }

let url: NSURL = NSURL(string: "http://example.com")!
let service1 = container.resolve(url, 80) as Service
let service2 = container.resolve(80, url) as Service
let service3 = container.resolve(80, NSURL(string: "http://example.com")) as Service
let service4 = container.resolve(80, NSURL(string: "http://example.com")! as NSURL!) as Service

(service1 as! ServiceImp3).name
(service2 as! ServiceImp3).name
(service3 as! ServiceImp3).name
(service4 as! ServiceImp3).name
(service1 as! ServiceImp4).name
(service2 as! ServiceImp4).name
(service3 as! ServiceImp4).name
(service4 as! ServiceImp4).name

/*:
Note that all of the services were resolved using different factories.
Expand All @@ -35,8 +35,8 @@ _Dip_ supports up to six runtime arguments. If that is not enougth you can exten
*/

extension DependencyContainer {
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T>
}

public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,41 @@

import Dip

let container = DependencyContainer()

/*:

### Scopes

Dip supports two different scopes of objects: _Prototype_ and _Singleton_.
Dip supports three different scopes of objects: _Prototype_, _ObjectGraph_ and _Singleton_.

* The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`.
* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls during the container lifetime.
* The `.ObjectGraph` scope is like `.Prototype` scope but it will make the `DependencyContainer` to reuse resolved instances during one call to `resolve` method. When this call returns all resolved insances will be discarded and next call to `resolve` will produce new instances. This scope should be used to resolve [circular dependencies](Circular%20dependencies).
* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls to `resolve` during the container lifetime.

The `.Prototype` scope is the default. To register a singleton, use `register(tag:instance:)`
The `.Prototype` scope is the default. To set a scope you pass it as an argument to `register` method.
*/

let container = DependencyContainer { container in
container.register(tag:"sharedService", instance: ServiceImp1() as Service)
container.register { ServiceImp1() as Service }
}

let sharedService = container.resolve(tag: "sharedService") as Service
let sameSharedService = container.resolve(tag: "sharedService") as Service
sharedService as! ServiceImp1 === sameSharedService as! ServiceImp1
container.register { ServiceImp1() as Service }
container.register(tag: "prototype", .Prototype) { ServiceImp1() as Service }
container.register(tag: "object graph", .ObjectGraph) { ServiceImp2() as Service }
container.register(tag: "shared instance", .Singleton) { ServiceImp3() as Service }

let service = container.resolve() as Service
let anotherService = container.resolve() as Service
service as! ServiceImp1 === anotherService as! ServiceImp1

//: [Next: Shared Instances](@next)
let prototypeService = container.resolve(tag: "prototype") as Service
let anotherPrototypeService = container.resolve(tag: "prototype") as Service
prototypeService as! ServiceImp1 === anotherPrototypeService as! ServiceImp1

let graphService = container.resolve(tag: "object graph") as Service
let anotherGraphService = container.resolve(tag: "object graph") as Service
graphService as! ServiceImp2 === anotherGraphService as! ServiceImp2

let sharedService = container.resolve(tag: "shared instance") as Service
let sameSharedService = container.resolve(tag: "shared instance") as Service
sharedService as! ServiceImp3 === sameSharedService as! ServiceImp3

//: [Next: Circular Dependencies](@next)

Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Sure, this is very easy to code indeed. And nothing bad so far.
But probably if you wrote a unit test or integration test for that code first, you would have noticed a problem earilier. How you test that code? And how you ensure that your tests are idenpendent of the API client's state from the previous test?
Of cource you can work around all of the problems and the fact that `ApiClient` is a singleton, reset it's state somehow, or mock a class so that it will not return a singleton instance. But look - a moment before the singleton was your best friend and now you are fighting against it.

Think - why do you want API client to be a singleton in a first place? To queue or throttle requests? Then do your queue or throttler a singleton, not an API client. Or is there any other reason. Most likely API client itself does not have a requirement to have only one system during the whole lifecycle of your application. Imagine that in the future we need two API Clients, because you now have to address two different servers & plaforms? Imposing that singleton restricts now your flexibility a lot.
Think - why do you want API client to be a singleton in a first place? To queue or throttle requests? Then do your queue or throttler a singleton, not an API client. Or is there any other reason. Most likely API client itself does not have a requirement to have one and only one instance during the lifecycle of your application. Imagine that in the future we need two API Clients, because you now have to address two different servers & plaforms? Imposing that singleton restricts now your flexibility a lot.

Instead, inject API client in view controller with property injection or constructor injection.
*/
Expand Down Expand Up @@ -125,7 +125,7 @@ class DipViewController: UIViewController {
var dipController = DipViewController(dependencies: container)

/*:
Of cource `DependencyContainer` should not be used as singleton too. Instead, inject it to objects that need to access it. And use a protocol for that. For example if your view controller needs to access API client, it does not need a reference to `DependencyContainer`, it only needs a reference to _something_ that can provide it an API client instance.
Of cource `DependencyContainer` should not be a singleton too. Instead, inject it to objects that need to access it. And use a protocol for that. For example if your view controller needs to access API client, it does not need a reference to `DependencyContainer`, it only needs a reference to _something_ that can provide it an API client instance.
*/

protocol ApiClientProvider {
Expand Down
12 changes: 10 additions & 2 deletions DipPlayground.playground/Sources/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ public class ServiceImp1: Service {
public class ServiceImp2: Service {
public init() {}
}

public class ServiceImp3: Service {
public init() {}
}

public class ServiceImp4: Service {

public let name: String

Expand All @@ -19,7 +22,7 @@ public class ServiceImp3: Service {

}

public protocol Client {
public protocol Client: class {
var service: Service {get}
init(service: Service)
}
Expand All @@ -45,3 +48,8 @@ public class ServiceFactory {
}
}

public class ClientServiceImp: Service {
public weak var client: Client?
public init() {}
}

3 changes: 2 additions & 1 deletion DipPlayground.playground/contents.xcplayground
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='rendered'>
<playground version='6.0' target-platform='ios' display-mode='raw'>
<pages>
<page name='What is Dip?'/>
<page name='Creating container'/>
<page name='Registering components'/>
<page name='Resolving components'/>
<page name='Runtime arguments'/>
<page name='Scopes'/>
<page name='Circular dependencies'/>
<page name='Shared Instances'/>
<page name='Testing'/>
</pages>
Expand Down

0 comments on commit f3368ee

Please sign in to comment.