Skip to content

Commit 932b27c

Browse files
committed
auto injection playground page and updated documentation
1 parent 099e0f6 commit 932b27c

File tree

8 files changed

+257
-5
lines changed

8 files changed

+257
-5
lines changed

Diff for: CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGELOG
22

3+
## Develop
4+
5+
* Added auto-injection feature
6+
[#13](https://github.com/AliSoftware/Dip/pull/13), [@ilyapuchka](https://github.com/ilyapuchka)
7+
38
## 4.0.0
49

510
#### New Features

Diff for: Dip/Dip/AutoInjection.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ extension DependencyContainer {
121121

122122
- Note:
123123
Use `InjectedWeak<T>` to define one of two circular dependecies if another dependency is defined as `Injected<U>`.
124-
This will prevent retain cycle between resolved instances.
124+
This will prevent a retain cycle between resolved instances.
125+
126+
- 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.
125127

126128
**Example**:
127129
```swift
@@ -134,7 +136,7 @@ extension DependencyContainer {
134136
}
135137

136138
//when resolved client will have service injected
137-
let client = container.resolve() as Client
139+
let client = try! container.resolve() as Client
138140

139141
```
140142

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
//: [Previous: Shared Instances](@previous)
2+
3+
import UIKit
4+
import Dip
5+
6+
let container = DependencyContainer()
7+
/*:
8+
9+
### Auto-Injection
10+
11+
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:
12+
*/
13+
14+
protocol Service: class {
15+
var logger: Logger? { get set }
16+
var tracker: Tracker? { get set }
17+
}
18+
19+
class ServiceImp: Service {
20+
var logger: Logger?
21+
var tracker: Tracker?
22+
}
23+
24+
container.register() { TrackerImp() as Tracker }
25+
container.register() { LoggerImp() as Logger }
26+
27+
container.register() { ServiceImp() as Service }
28+
.resolveDependencies { container, service in
29+
service.logger = try! container.resolve() as Logger
30+
service.tracker = try! container.resolve() as Tracker
31+
}
32+
33+
let service = try! container.resolve() as Service
34+
service.logger
35+
service.tracker
36+
37+
/*:
38+
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)).
39+
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.
40+
41+
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:
42+
*/
43+
44+
class AutoInjectedServiceImp: Service {
45+
private var injectedLogger = Injected<Logger>()
46+
var logger: Logger? { get { return injectedLogger.value } set { injectedLogger.value = newValue } }
47+
48+
private var injectedTracker = Injected<Tracker>()
49+
var tracker: Tracker? { get { return injectedTracker.value } set { injectedTracker.value = newValue } }
50+
}
51+
52+
container.register() { AutoInjectedServiceImp() as Service }
53+
54+
let autoInjectedService = try! container.resolve() as Service
55+
autoInjectedService.logger
56+
autoInjectedService.tracker
57+
58+
/*:
59+
The same you can do if you already have an instance of service and just want to resolve its dependencies:
60+
*/
61+
62+
let providedService = AutoInjectedServiceImp()
63+
container.resolveDependencies(providedService)
64+
providedService.logger
65+
providedService.tracker
66+
67+
/*:
68+
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`).
69+
70+
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.
71+
72+
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:
73+
*/
74+
75+
protocol Server: class {
76+
weak var client: ServerClient? {get set}
77+
}
78+
79+
protocol ServerClient: class {
80+
var server: Server? {get}
81+
}
82+
83+
class ServerImp: Server {
84+
weak var client: ServerClient?
85+
}
86+
87+
class ServerClientImp: ServerClient {
88+
var server: Server?
89+
90+
init(server: Server) {
91+
self.server = server
92+
}
93+
}
94+
95+
container.register(.ObjectGraph) {
96+
ServerClientImp(server: try! container.resolve()) as ServerClient
97+
}
98+
99+
container.register(.ObjectGraph) { ServerImp() as Server }
100+
.resolveDependencies { container, server in
101+
server.client = try! container.resolve() as ServerClient
102+
}
103+
104+
let client = try! container.resolve() as ServerClient
105+
client.server
106+
107+
/*:
108+
With auto-injection you will have the following code:
109+
*/
110+
111+
class InjectedServerImp: Server {
112+
private var injectedClient = InjectedWeak<ServerClient>()
113+
var client: ServerClient? { get { return injectedClient.value } set { injectedClient.value = newValue }}
114+
}
115+
116+
class InjectedClientImp: ServerClient {
117+
private var injectedServer = Injected<Server>()
118+
var server: Server? { get { return injectedServer.value} }
119+
}
120+
121+
container.register(.ObjectGraph) { InjectedServerImp() as Server }
122+
container.register(.ObjectGraph) { InjectedClientImp() as ServerClient }
123+
124+
let injectedClient = try! container.resolve() as ServerClient
125+
injectedClient.server
126+
injectedClient.server?.client === injectedClient //circular dependencies were resolved correctly
127+
128+
/*:
129+
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.
130+
131+
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:
132+
*/
133+
container.register() { RouterImp() as Router }
134+
container.register() { DataProviderImp() as DataProvider }
135+
136+
class ViewController: UIViewController {
137+
var logger: Logger?
138+
var tracker: Tracker?
139+
var dataProvider: DataProvider?
140+
var router: Router?
141+
142+
//it's better not to access container directly in implementation but that's ok for illustration
143+
func injectDependencies(container: DependencyContainer) {
144+
logger = try! container.resolve() as Logger
145+
tracker = try! container.resolve() as Tracker
146+
dataProvider = try! container.resolve() as DataProvider
147+
router = try! container.resolve() as Router
148+
}
149+
}
150+
151+
let viewController = ViewController()
152+
viewController.injectDependencies(container)
153+
viewController.router
154+
155+
/*:
156+
With auto-injection you can replace that with something like this:
157+
*/
158+
159+
class AutoInjectedViewController: UIViewController {
160+
161+
var logger = Injected<Logger>()
162+
var tracker = Injected<Tracker>()
163+
var dataProvider = Injected<DataProvider>()
164+
var router = Injected<Router>()
165+
166+
func injectDependencies(container: DependencyContainer) {
167+
container.resolveDependencies(self)
168+
}
169+
}
170+
171+
let autoViewController = AutoInjectedViewController()
172+
autoViewController.injectDependencies(container)
173+
autoViewController.router.value
174+
175+
/*:
176+
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.
177+
178+
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.
179+
180+
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.
181+
*/
182+
183+
//: [Next: Testing](@next)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Timeline
3+
version = "3.0">
4+
<TimelineItems>
5+
</TimelineItems>
6+
</Timeline>

Diff for: DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ dipController = DipViewController(apiClientProvider: container)
152152
/*:
153153
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.
154154

155-
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.
155+
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.
156156

157157
*/
158158

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

161161

Diff for: DipPlayground.playground/Sources/Models.swift

+23
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,26 @@ public class ClientServiceImp: Service {
5353
public init() {}
5454
}
5555

56+
public protocol Logger {}
57+
public protocol Tracker {}
58+
public protocol DataProvider {}
59+
public protocol Router {}
60+
61+
public class LoggerImp: Logger {
62+
public init() {}
63+
}
64+
65+
public class TrackerImp: Tracker {
66+
public init() {}
67+
}
68+
69+
public class RouterImp: Router {
70+
public init() {}
71+
}
72+
73+
public class DataProviderImp: DataProvider {
74+
public init() {}
75+
}
76+
77+
78+

Diff for: DipPlayground.playground/contents.xcplayground

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<playground version='6.0' target-platform='ios' display-mode='raw'>
2+
<playground version='6.0' target-platform='ios' display-mode='rendered'>
33
<pages>
44
<page name='What is Dip?'/>
55
<page name='Creating container'/>
@@ -9,6 +9,7 @@
99
<page name='Scopes'/>
1010
<page name='Circular dependencies'/>
1111
<page name='Shared Instances'/>
12+
<page name='Auto-injection'/>
1213
<page name='Testing'/>
1314
</pages>
1415
</playground>

Diff for: README.md

+32
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,44 @@ container.register(.ObjectGraph) { ServerImp() as Server }
178178
```
179179
More infromation about circular dependencies you can find in a playground.
180180

181+
### Auto-Injections
182+
183+
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.
184+
185+
```swift
186+
protocol Server {
187+
weak var client: Client? { get }
188+
}
189+
190+
protocol Client: class {
191+
var server: Server? { get }
192+
}
193+
194+
class ServerImp: Server {
195+
private var injectedClient = InjectedWeak<Client>()
196+
var client: Client? { return injectedClient.value }
197+
}
198+
199+
class ClientImp: Client {
200+
private var injectedServer = Injected<Server>()
201+
var server: Server? { get { return injectedServer.value} }
202+
}
203+
204+
container.register(.ObjectGraph) { ServerImp() as Server }
205+
container.register(.ObjectGraph) { ClientImp() as Client }
206+
207+
let client = try! container.resolve() as Client
208+
209+
```
210+
You can find more use cases for auto-injection in a Playground.
211+
181212
### Thread safety
182213

183214
_Dip_ does not provide thread safety, so you need to make sure you always call `resolve` method of `DependencyContainer` from the single thread.
184215
Otherwise if two threads try to resolve the same type they can get different instances where the same instance is expected.
185216

186217
### Errors
218+
187219
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
188220
`DefinitionNotFond(DefinitionKey)` if it failed to resolve type. When calling `resolve` you need to use a `try` operator.
189221
There are rare use cases where your application can recover from this kind of errors (for example you can register new types

0 commit comments

Comments
 (0)