Skip to content

Commit 1e9e4c2

Browse files
Ilya Puchkailyapuchka
Ilya Puchka
authored andcommitted
auto-injection
1 parent c6bf181 commit 1e9e4c2

File tree

6 files changed

+565
-43
lines changed

6 files changed

+565
-43
lines changed

Dip/Dip.xcodeproj/project.pbxproj

+18
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@
3838
0919F4EC1C16419500DC3B10 /* DefinitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */; };
3939
0919F4ED1C16419500DC3B10 /* RuntimeArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */; };
4040
0919F4EE1C16419500DC3B10 /* ComponentScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */; };
41+
09873F561C1E0237000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
42+
09873F771C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */; };
43+
09873F781C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */; };
44+
09873F791C1E024F000C02F6 /* AutoInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */; };
45+
09873F7A1C1E0252000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
46+
09873F7B1C1E0253000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
47+
09873F7C1C1E0254000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
4148
/* End PBXBuildFile section */
4249

4350
/* Begin PBXContainerItemProxy section */
@@ -82,6 +89,8 @@
8289
0919F4D01C16417000DC3B10 /* DipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DipTests.swift; sourceTree = "<group>"; };
8390
0919F4D11C16417000DC3B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8491
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeArgumentsTests.swift; sourceTree = "<group>"; };
92+
09873F551C1E0237000C02F6 /* AutoInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInjection.swift; sourceTree = "<group>"; };
93+
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInjectionTests.swift; sourceTree = "<group>"; };
8594
/* End PBXFileReference section */
8695

8796
/* Begin PBXFrameworksBuildPhase section */
@@ -147,6 +156,7 @@
147156
0919F4CA1C16417000DC3B10 /* Dip.swift */,
148157
0919F4C81C16417000DC3B10 /* Definition.swift */,
149158
0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */,
159+
09873F551C1E0237000C02F6 /* AutoInjection.swift */,
150160
0919F4CB1C16417000DC3B10 /* Info.plist */,
151161
);
152162
path = Dip;
@@ -159,6 +169,7 @@
159169
0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */,
160170
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */,
161171
0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */,
172+
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */,
162173
0919F4D11C16417000DC3B10 /* Info.plist */,
163174
);
164175
path = DipTests;
@@ -468,6 +479,7 @@
468479
files = (
469480
0919F4D51C16417B00DC3B10 /* Definition.swift in Sources */,
470481
0919F4D41C16417B00DC3B10 /* Dip.swift in Sources */,
482+
09873F561C1E0237000C02F6 /* AutoInjection.swift in Sources */,
471483
0919F4D61C16417B00DC3B10 /* RuntimeArguments.swift in Sources */,
472484
);
473485
runOnlyForDeploymentPostprocessing = 0;
@@ -480,6 +492,7 @@
480492
0919F4E41C16419300DC3B10 /* DefinitionTests.swift in Sources */,
481493
0919F4E31C16419300DC3B10 /* DipTests.swift in Sources */,
482494
0919F4E51C16419300DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
495+
09873F771C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */,
483496
);
484497
runOnlyForDeploymentPostprocessing = 0;
485498
};
@@ -489,6 +502,7 @@
489502
files = (
490503
0919F4D91C16417C00DC3B10 /* Definition.swift in Sources */,
491504
0919F4D81C16417C00DC3B10 /* Dip.swift in Sources */,
505+
09873F7A1C1E0252000C02F6 /* AutoInjection.swift in Sources */,
492506
0919F4DA1C16417C00DC3B10 /* RuntimeArguments.swift in Sources */,
493507
);
494508
runOnlyForDeploymentPostprocessing = 0;
@@ -501,6 +515,7 @@
501515
0919F4E81C16419400DC3B10 /* DefinitionTests.swift in Sources */,
502516
0919F4E71C16419400DC3B10 /* DipTests.swift in Sources */,
503517
0919F4E91C16419400DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
518+
09873F781C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */,
504519
);
505520
runOnlyForDeploymentPostprocessing = 0;
506521
};
@@ -510,6 +525,7 @@
510525
files = (
511526
0919F4DD1C16417D00DC3B10 /* Definition.swift in Sources */,
512527
0919F4DC1C16417D00DC3B10 /* Dip.swift in Sources */,
528+
09873F7B1C1E0253000C02F6 /* AutoInjection.swift in Sources */,
513529
0919F4DE1C16417D00DC3B10 /* RuntimeArguments.swift in Sources */,
514530
);
515531
runOnlyForDeploymentPostprocessing = 0;
@@ -522,6 +538,7 @@
522538
0919F4EC1C16419500DC3B10 /* DefinitionTests.swift in Sources */,
523539
0919F4EB1C16419500DC3B10 /* DipTests.swift in Sources */,
524540
0919F4ED1C16419500DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
541+
09873F791C1E024F000C02F6 /* AutoInjectionTests.swift in Sources */,
525542
);
526543
runOnlyForDeploymentPostprocessing = 0;
527544
};
@@ -531,6 +548,7 @@
531548
files = (
532549
0919F4E11C16417E00DC3B10 /* Definition.swift in Sources */,
533550
0919F4E01C16417E00DC3B10 /* Dip.swift in Sources */,
551+
09873F7C1C1E0254000C02F6 /* AutoInjection.swift in Sources */,
534552
0919F4E21C16417E00DC3B10 /* RuntimeArguments.swift in Sources */,
535553
);
536554
runOnlyForDeploymentPostprocessing = 0;

Dip/Dip/AutoInjection.swift

+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
//
2+
// Dip
3+
//
4+
// Copyright (c) 2015 Olivier Halligon <[email protected]>
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
//
24+
25+
//MARK: Public
26+
27+
/**
28+
Use this wrapper to identifiy strong properties of the instance that should be injected when you call
29+
`resolveDependencies()` on this instance. Type T can be any type.
30+
31+
- warning:
32+
Do not define this property as optional or container will not be able to inject it.
33+
Instead define it with initial value of `Injected<T>()`.
34+
If you need to nilify wrapped value, assing property to `Injected<T>()`.
35+
36+
**Example**:
37+
38+
```swift
39+
class ClientImp: Client {
40+
var service = Injected<Service>()
41+
}
42+
43+
```
44+
- seealso: `InjectedWeak`, `DependencyContainer.resolveDependencies(_:)`
45+
46+
*/
47+
public final class Injected<T>: _InjectedPropertyBox {
48+
49+
static var tag: DependencyContainer.Tag {
50+
return .String("\(Injected<T>.self)")
51+
}
52+
53+
var _value: Any?
54+
55+
public var value: T? {
56+
get {
57+
return _value as? T
58+
}
59+
set {
60+
_value = newValue
61+
}
62+
}
63+
64+
public init() {}
65+
66+
}
67+
68+
/**
69+
Use this wrapper to identifiy weak properties of the instance that should be injected when you call
70+
`resolveDependencies()` on this instance. Type T should be a **class** type.
71+
Otherwise it will cause runtime exception when container will try to resolve the property.
72+
Use this wrapper to define one of two circular dependencies to avoid retain cycle.
73+
74+
- warning:
75+
Do not define this property as optional or container will not be able to inject it.
76+
Instead define it with initial value of `InjectedWeak<T>()`.
77+
If you need to nilify wrapped value, assing property to `InjectedWeak<T>()`.
78+
79+
**Example**:
80+
81+
```swift
82+
class ServiceImp: Service {
83+
var client = InjectedWeak<Client>()
84+
}
85+
86+
```
87+
88+
- note:
89+
The only difference between `InjectedWeak` and `Injected` is that `InjectedWeak` uses _weak_ reference
90+
to store underlying value, when `Injected` uses _strong_ reference.
91+
For that reason if you resolve instance that holds weakly injected property
92+
this property will be released when `resolve` returns 'cause no one else holds reference to it.
93+
94+
- seealso: `Injected`, `DependencyContainer.resolveDependencies(_:)`
95+
96+
*/
97+
public final class InjectedWeak<T>: _InjectedWeakPropertyBox {
98+
99+
//Only classes (means AnyObject) can be used as `weak` properties
100+
//but we can not make <T: AnyObject> cause that will prevent using protocol as generic type
101+
//so we just rely on user reading documentation and passing AnyObject in runtime
102+
//also we will throw fatal error if type can not be casted to AnyObject during resolution
103+
104+
static var tag: DependencyContainer.Tag {
105+
return .String("\(InjectedWeak<T>.self)")
106+
}
107+
108+
weak var _value: AnyObject?
109+
110+
public var value: T? {
111+
get {
112+
return _value as? T
113+
}
114+
set {
115+
_value = newValue as? AnyObject
116+
}
117+
}
118+
119+
public init() {}
120+
121+
}
122+
123+
extension DependencyContainer {
124+
125+
/**
126+
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.
127+
128+
- parameter instance: object whose dependecies should be resolved
129+
130+
- Note:
131+
Use `InjectedWeak<T>` to define one of two circular dependecies if another dependency is defined as `Injected<U>`.
132+
This will prevent retain cycle between resolved instances.
133+
134+
**Example**:
135+
```swift
136+
class ClientImp: Client {
137+
var service = Injected<Service>()
138+
}
139+
140+
class ServiceImp: Service {
141+
var client = InjectedWeak<Client>()
142+
}
143+
144+
//when resolved client will have service injected
145+
let client = container.resolve() as Client
146+
147+
```
148+
149+
*/
150+
public func resolveDependencies(instance: Any) {
151+
for child in Mirror(reflecting: instance).children {
152+
do {
153+
try (child.value as? _AutoInjectedPropertyBox)?.resolve(self)
154+
} catch {
155+
print(error)
156+
}
157+
}
158+
}
159+
160+
}
161+
162+
//MARK: - Private
163+
164+
typealias InjectedFactory = ()->Any
165+
typealias InjectedWeakFactory = ()->AnyObject
166+
167+
extension DependencyContainer {
168+
169+
func registerInjected(definition: AutoInjectedDefinition) {
170+
guard let key = definition.injectedKey,
171+
definition = definition.injectedDefinition else { return }
172+
definitions[key] = definition
173+
}
174+
175+
func registerInjectedWeak(definition: AutoInjectedDefinition) {
176+
guard let key = definition.injectedWeakKey,
177+
definition = definition.injectedWeakDefinition else { return }
178+
definitions[key] = definition
179+
}
180+
181+
func removeInjected(definition: AutoInjectedDefinition) {
182+
guard definition.injectedDefinition != nil else { return }
183+
definitions[definition.injectedKey] = nil
184+
}
185+
186+
func removeInjectedWeak(definition: AutoInjectedDefinition) {
187+
guard definition.injectedWeakDefinition != nil else { return }
188+
definitions[definition.injectedWeakKey] = nil
189+
}
190+
191+
}
192+
193+
protocol _AutoInjectedPropertyBox {
194+
func resolve(container: DependencyContainer) throws
195+
static var tag: DependencyContainer.Tag { get }
196+
}
197+
198+
protocol _InjectedPropertyBox: class, _AutoInjectedPropertyBox {
199+
var _value: Any? { get set }
200+
}
201+
202+
extension _InjectedPropertyBox {
203+
func resolve(container: DependencyContainer) throws {
204+
self._value = try container.resolve(tag: self.dynamicType.tag) as Any
205+
}
206+
}
207+
208+
protocol _InjectedWeakPropertyBox: class, _AutoInjectedPropertyBox {
209+
weak var _value: AnyObject? { get set }
210+
}
211+
212+
extension _InjectedWeakPropertyBox {
213+
func resolve(container: DependencyContainer) throws {
214+
self._value = try container.resolve(tag: self.dynamicType.tag) as AnyObject
215+
}
216+
}
217+
218+
func isInjectedTag(tag: DependencyContainer.Tag?) -> String? {
219+
guard let tag = tag else { return nil }
220+
guard case let .String(stringTag) = tag else { return nil }
221+
222+
return try! stringTag.match("^Injected(?:Weak){0,1}<\\((.+)\\)>$")?.first
223+
}
224+
225+
extension String {
226+
private func match(pattern: String) throws -> [String]? {
227+
let expr = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions())
228+
let result = expr.firstMatchInString(self, options: NSMatchingOptions(), range: NSMakeRange(0, characters.count))
229+
if let result = result {
230+
let groups = (1..<result.numberOfRanges).map {
231+
(self as NSString).substringWithRange(result.rangeAtIndex($0))
232+
}
233+
return groups
234+
}
235+
else {
236+
return nil
237+
}
238+
}
239+
}
240+
241+

0 commit comments

Comments
 (0)