Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f9c54e484 | |||
| ec177fba78 | |||
| 6b6f85ba4c | |||
| 5f845e6701 | |||
| 0028083289 | |||
| 10dd5a51a5 | |||
| 4e3a53997e | |||
| ae8d56e5d8 | |||
| bc303da493 | |||
| 0db3155835 | |||
| 74112c6051 | |||
| a98a9eed56 | |||
| a4f6db4f8d | |||
| a74b527324 |
@@ -1,5 +1,17 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Moved from generic _tag_ parameter on container to `Tag` enum with `String` and `Int` cases
|
||||
[#3](https://github.com/AliSoftware/Dip/pull/3), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
> This API change allows easier use of `DependencyContainer` and avoid some constraints. For a complete rationale on that change, see [PR #3](https://github.com/AliSoftware/Dip/pull/3).
|
||||
|
||||
## 1.0.1
|
||||
|
||||
* Improved README
|
||||
* Imrpoved discoverability using keywords in `podspec`
|
||||
|
||||
## 1.0.0
|
||||
|
||||
#### Dip
|
||||
|
||||
+3
-5
@@ -1,11 +1,11 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "Dip"
|
||||
s.version = "1.0.0"
|
||||
s.summary = "A simple Dependency Resolver (Dependency Injection using Protocol resolution)."
|
||||
s.version = "2.0.0"
|
||||
s.summary = "A simple Dependency Resolver: Dependency Injection using Protocol resolution."
|
||||
|
||||
s.description = <<-DESC
|
||||
Dip is a Swift framework to manage your Dependencies between your classes
|
||||
in your app.
|
||||
in your app using Dependency Injection.
|
||||
|
||||
It's aimed to be very simple to use while improving testability
|
||||
of your app by allowing you to get rid of those sharedInstances and instead
|
||||
@@ -15,8 +15,6 @@ Pod::Spec.new do |s|
|
||||
an instance dynamically in your classes. Then your App and your Tests can be
|
||||
configured to resolve the protocol using a different instance or class so this
|
||||
improve testability by decoupling the API and the concrete class used to implement it.
|
||||
|
||||
It's not real Dependency Injection _per se_, but it's close.
|
||||
DESC
|
||||
|
||||
s.homepage = "https://github.com/AliSoftware/Dip"
|
||||
|
||||
@@ -19,7 +19,7 @@ private let FAKE_STARSHIPS = false
|
||||
|
||||
|
||||
// MARK: Dependency Container for WebServices & NetworkLayer
|
||||
let wsDependencies = DependencyContainer<WebService>() { dip in
|
||||
let wsDependencies = DependencyContainer() { dip in
|
||||
|
||||
// Register the NetworkLayer, same for everyone here (but we have the ability to register a different one for a specific WebService if we wanted to)
|
||||
dip.register(instance: URLSessionNetworkLayer(baseURL: "http://swapi.co/api/")! as NetworkLayer)
|
||||
@@ -27,9 +27,8 @@ let wsDependencies = DependencyContainer<WebService>() { dip in
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: Dependency Container for Providers
|
||||
let providerDependencies = DependencyContainer<Int>() { dip in
|
||||
let providerDependencies = DependencyContainer() { dip in
|
||||
|
||||
if FAKE_PERSONS {
|
||||
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Dip
|
||||
|
||||
enum SWAPIError: ErrorType {
|
||||
case InvalidJSON
|
||||
}
|
||||
|
||||
enum WebService {
|
||||
enum WebService: String {
|
||||
case PersonWS
|
||||
case StarshipWS
|
||||
var tag: DependencyContainer.Tag { return DependencyContainer.Tag.String(self.rawValue) }
|
||||
}
|
||||
|
||||
func idFromURLString(urlString: String) -> Int? {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
struct SWAPIPersonProvider : PersonProviderAPI {
|
||||
let ws = wsDependencies.resolve(.PersonWS) as NetworkLayer
|
||||
let ws = wsDependencies.resolve(WebService.PersonWS.tag) as NetworkLayer
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
ws.request("people") { response in
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
struct SWAPIStarshipProvider : StarshipProviderAPI {
|
||||
let ws = wsDependencies.resolve(.StarshipWS) as NetworkLayer
|
||||
let ws = wsDependencies.resolve(WebService.StarshipWS.tag) as NetworkLayer
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
ws.request("starships") { response in
|
||||
|
||||
@@ -17,7 +17,7 @@ class PersonListViewController: UITableViewController, FetchableTrait {
|
||||
return provider.fetchIDs(completion)
|
||||
}
|
||||
func fetchOne(personID: Int, completion: Person? -> Void) {
|
||||
let provider = providerDependencies.resolve(personID) as PersonProviderAPI
|
||||
let provider = providerDependencies.resolve(.Int(personID)) as PersonProviderAPI
|
||||
return provider.fetch(personID, completion: completion)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,15 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Dip
|
||||
|
||||
class StarshipListViewController : UITableViewController, FetchableTrait {
|
||||
var objects: [Starship]?
|
||||
var batchRequestID = 0
|
||||
|
||||
private func provider(tag:Int?) -> StarshipProviderAPI { return providerDependencies.resolve(tag) }
|
||||
private func provider(tag:Int?) -> StarshipProviderAPI {
|
||||
return providerDependencies.resolve(tag.flatMap { .Int($0) })
|
||||
}
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
provider(nil).fetchIDs(completion)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PODS:
|
||||
- Dip (1.0.0)
|
||||
- Dip (2.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Dip (from `../`)
|
||||
@@ -9,6 +9,6 @@ EXTERNAL SOURCES:
|
||||
:path: ../
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Dip: 25f7794871e836ba68d303e3ada4c0cdbe1afac7
|
||||
Dip: ae784a32e584d5c57809b2cdc4fd2f287d075f7a
|
||||
|
||||
COCOAPODS: 0.39.0
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "Dip",
|
||||
"version": "1.0.0",
|
||||
"summary": "A simple Dependency Resolver (Simplified Dependency Injection-like resolution).",
|
||||
"description": "Dip is a Swift framework to manage your Dependencies between your classes\nin your app.\n\nIt's aimed to be very simple to use while improving testability\nof your app by allowing you to get rid of those sharedInstances and instead\ninject values based on protocol resolution.\n\nDefine your API using a protocol, then ask Dip to resolve this protocol into\nan instance dynamically in your classes. Then your App and your Tests can be\nconfigured to resolve the protocol using a different instance or class so this\nimprove testability by decoupling the API and the concrete class used to implement it.\n\nIt's not real Dependency Injection _per se_, but it's close.",
|
||||
"version": "2.0.0",
|
||||
"summary": "A simple Dependency Resolver: Dependency Injection using Protocol resolution.",
|
||||
"description": "Dip is a Swift framework to manage your Dependencies between your classes\nin your app using Dependency Injection.\n\nIt's aimed to be very simple to use while improving testability\nof your app by allowing you to get rid of those sharedInstances and instead\ninject values based on protocol resolution.\n\nDefine your API using a protocol, then ask Dip to resolve this protocol into\nan instance dynamically in your classes. Then your App and your Tests can be\nconfigured to resolve the protocol using a different instance or class so this\nimprove testability by decoupling the API and the concrete class used to implement it.",
|
||||
"homepage": "https://github.com/AliSoftware/Dip",
|
||||
"license": "MIT",
|
||||
"authors": {
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"source": {
|
||||
"git": "https://github.com/AliSoftware/Dip.git",
|
||||
"tag": "1.0.0"
|
||||
"tag": "2.0.0"
|
||||
},
|
||||
"social_media_url": "https://twitter.com/aligatr",
|
||||
"platforms": {
|
||||
|
||||
Generated
+2
-2
@@ -1,5 +1,5 @@
|
||||
PODS:
|
||||
- Dip (1.0.0)
|
||||
- Dip (2.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Dip (from `../`)
|
||||
@@ -9,6 +9,6 @@ EXTERNAL SOURCES:
|
||||
:path: ../
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Dip: 25f7794871e836ba68d303e3ada4c0cdbe1afac7
|
||||
Dip: ae784a32e584d5c57809b2cdc4fd2f287d075f7a
|
||||
|
||||
COCOAPODS: 0.39.0
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -14,7 +14,7 @@
|
||||
buildForArchiving = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = 'primary'
|
||||
BlueprintIdentifier = 'C1B5AE1707BA9907C05BEC25'
|
||||
BlueprintIdentifier = '0CFBD42E9A9E0A9CBD1C8DD7'
|
||||
BlueprintName = 'Dip'
|
||||
ReferencedContainer = 'container:Pods.xcodeproj'
|
||||
BuildableName = 'Dip.framework'>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<string>2.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import Dip
|
||||
|
||||
var wsDependencies = DependencyContainer<WebService>()
|
||||
var wsDependencies = DependencyContainer()
|
||||
|
||||
// MARK: - Mock object used for tests
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import Dip
|
||||
|
||||
class SWAPIPersonProviderTests: XCTestCase {
|
||||
let fakePerson1 = ["name": "John Doe", "mass": "72", "height": "172", "eye_color": "brown", "hair_color": "black", "gender": "male",
|
||||
@@ -22,7 +23,7 @@ class SWAPIPersonProviderTests: XCTestCase {
|
||||
|
||||
func testFetchPersonIDs() {
|
||||
let mock = NetworkMock(json: ["results": [fakePerson1, fakePerson2]])
|
||||
wsDependencies.register(.PersonWS, instance: mock as NetworkLayer)
|
||||
wsDependencies.register(WebService.PersonWS.tag, instance: mock as NetworkLayer)
|
||||
|
||||
let provider = SWAPIPersonProvider()
|
||||
provider.fetchIDs { personIDs in
|
||||
@@ -37,7 +38,7 @@ class SWAPIPersonProviderTests: XCTestCase {
|
||||
func testFetchOnePerson() {
|
||||
|
||||
let mock = NetworkMock(json: fakePerson1)
|
||||
wsDependencies.register(.PersonWS, instance: mock as NetworkLayer)
|
||||
wsDependencies.register(WebService.PersonWS.tag, instance: mock as NetworkLayer)
|
||||
|
||||
let provider = SWAPIPersonProvider()
|
||||
provider.fetch(1) { person in
|
||||
@@ -57,7 +58,7 @@ class SWAPIPersonProviderTests: XCTestCase {
|
||||
func testFetchInvalidPerson() {
|
||||
let json = ["error":"whoops"]
|
||||
let mock = NetworkMock(json: json)
|
||||
wsDependencies.register(.PersonWS, instance: mock as NetworkLayer)
|
||||
wsDependencies.register(WebService.PersonWS.tag, instance: mock as NetworkLayer)
|
||||
|
||||
let provider = SWAPIPersonProvider()
|
||||
provider.fetch(12) { person in
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import Dip
|
||||
|
||||
class SWAPIStarshipProviderTests: XCTestCase {
|
||||
let fakeShip1 = ["name": "Falcon", "model": "Fighter", "manufacturer": "Fake Industries", "crew": "7", "passengers": "15",
|
||||
@@ -22,7 +23,7 @@ class SWAPIStarshipProviderTests: XCTestCase {
|
||||
|
||||
func testFetchStarshipIDs() {
|
||||
let mock = NetworkMock(json: ["results": [fakeShip1, fakeShip2]])
|
||||
wsDependencies.register(.StarshipWS, instance: mock as NetworkLayer)
|
||||
wsDependencies.register(WebService.StarshipWS.tag, instance: mock as NetworkLayer)
|
||||
|
||||
let provider = SWAPIStarshipProvider()
|
||||
provider.fetchIDs { shipIDs in
|
||||
@@ -37,7 +38,7 @@ class SWAPIStarshipProviderTests: XCTestCase {
|
||||
func testFetchOneStarship() {
|
||||
|
||||
let mock = NetworkMock(json: fakeShip1)
|
||||
wsDependencies.register(.StarshipWS, instance: mock as NetworkLayer)
|
||||
wsDependencies.register(WebService.StarshipWS.tag, instance: mock as NetworkLayer)
|
||||
|
||||
let provider = SWAPIStarshipProvider()
|
||||
provider.fetch(1) { starship in
|
||||
@@ -56,7 +57,7 @@ class SWAPIStarshipProviderTests: XCTestCase {
|
||||
func testFetchInvalidStarship() {
|
||||
let json = ["error":"whoops"]
|
||||
let mock = NetworkMock(json: json)
|
||||
wsDependencies.register(.StarshipWS, instance: mock as NetworkLayer)
|
||||
wsDependencies.register(WebService.StarshipWS.tag, instance: mock as NetworkLayer)
|
||||
|
||||
let provider = SWAPIStarshipProvider()
|
||||
provider.fetch(12) { starship in
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
[](http://cocoapods.org/pods/Dip)
|
||||
[](http://cocoapods.org/pods/Dip)
|
||||
|
||||

|
||||
_Photo by [Matthew Hine](https://commons.wikimedia.org/wiki/File:Chocolate_con_churros_-_San_Ginés,_Madrid.jpg), cc-by-2.0_
|
||||

|
||||
_Photo courtesy of [www.kevinandamanda.com](http://www.kevinandamanda.com/recipes/appetizer/homemade-soft-cinnamon-sugar-pretzel-bites-with-salted-caramel-dipping-sauce.html)_
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -18,7 +18,7 @@ It's inspired by `.NET`'s [Unity Container](https://msdn.microsoft.com/library/f
|
||||
* You start by creating `let dc = DependencyContainer()` and **register all your dependencies, by associating a `protocol` to an `instance` or a `factory`**.
|
||||
* Then anywhere in your application, you can call `dc.resolve()` to **resolve a `protocol` into an instance of a concrete type** using that `DependencyContainer`.
|
||||
|
||||
This allows you to define the real, concrete types only in one place ([like this in your app](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/DependencyContainers.swift#L28-L33), and [in your `setUp` for your Unit Tests](https://github.com/AliSoftware/Dip/blob/master/Example/Tests/SWAPIWebServiceTests.swift#L36-L38)) and then [only work with `protocols` in your code](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/Providers/SWAPIStarshipProvider.swift#L12) (which only define an API contract), without worrying about the real implementation.
|
||||
This allows you to define the real, concrete types only in one place ([e.g. like this in your app](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/DependencyContainers.swift#L22-L27), and [resetting it in your `setUp` for each Unit Tests](https://github.com/AliSoftware/Dip/blob/master/Example/Tests/SWAPIPersonProviderTests.swift#L17-L21)) and then [only work with `protocols` in your code](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/Providers/SWAPIStarshipProvider.swift#L12) (which only define an API contract), without worrying about the real implementation.
|
||||
|
||||
## Advantages of DI and loose coupling
|
||||
|
||||
@@ -46,8 +46,7 @@ First, create a `DependencyContainer` and use it to register instances and facto
|
||||
* `register(factory: _)` will register an instance factory — which generates a new instance each time you `resolve()`.
|
||||
* You need **cast the instance to the protocol type** you want to register it with (e.g. `register(instance: PlistUsersProvider() as UsersListProviderType)`).
|
||||
|
||||
Typically, to register your dependencies as early as possible in your app life-cycle, you will declare a `let dip: DependencyContainer = { … }()` somewhere (for example [at the top of your `AppDelegate.swift`](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/AppDelegate.swift#L12-L21)).
|
||||
In your (non-hosted, standalone) unit tests, you'll probably [declare them in your `func setUp()`](https://github.com/AliSoftware/Dip/blob/master/Example/Tests/SWAPIWebServiceTests.swift#L36-L38) instead.
|
||||
Typically, to register your dependencies as early as possible in your app life-cycle, you will declare a `let dip: DependencyContainer = { … }()` somewhere (for example [in a dedicated `.swift` file](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/DependencyContainers.swift#L22-L27)). In your (non-hosted, standalone) unit tests, you'll probably [reset them in your `func setUp()`](https://github.com/AliSoftware/Dip/blob/master/Example/Tests/SWAPIPersonProviderTests.swift#L17-L21) instead.
|
||||
|
||||
### Resolve dependencies
|
||||
|
||||
@@ -55,20 +54,60 @@ Typically, to register your dependencies as early as possible in your app life-c
|
||||
* Explicitly specify the return type of `resolve` so that Swift's type inference knows which protocol you're trying to resolve.
|
||||
* If that protocol was registered as a singleton instance (using `register(instance: …)`, the same instance will be returned each time you call `resolve()` for this protocol type. Otherwise, the instance factory will generate a new instance each time.
|
||||
|
||||
### Using block-based initialization
|
||||
|
||||
When calling the initializer of `DependencyContainer()`, you can pass a block that will be called right after the initialization. This allows you to have a nice syntax to do all your `register(…)` calls in there, instead of having to do them separately.
|
||||
|
||||
It may not seem to provide much, but given the fact that `DependencyContainers` are typically declared as global constants using a top-level `let`, it gets very useful, because instead of having to do it like this:
|
||||
|
||||
```swift
|
||||
let dip: DependencyContainer = {
|
||||
let dip = DependencyContainer()
|
||||
|
||||
dip.register(instance: ProductionEnvironment(analytics: true) as EnvironmentType)
|
||||
dip.register(instance: WebService() as WebServiceAPI)
|
||||
|
||||
return dip
|
||||
}()
|
||||
```
|
||||
|
||||
You can instead write this exact equivalent code, which is more compact, and indent better in Xcode (as the final closing brack is properly aligned):
|
||||
|
||||
```swift
|
||||
let dip = DependencyContainer { dip in
|
||||
dip.register(instance: ProductionEnvironment(analytics: true) as EnvironmentType)
|
||||
dip.register(instance: WebService() as WebServiceAPI)
|
||||
}
|
||||
```
|
||||
|
||||
### Using tags to associate various factories to one protocol
|
||||
|
||||
* If you give a `tag` in the parameter to `register()`, it will associate that instance or factory with this tag, which can be used later during `resolve` (see below).
|
||||
* `resolve(tag)` will try to find an instance (or instance factory) that match both the requested protocol _and_ the tag. If it doesn't find any, it will fallback to an instance (or instance factory) that only match the requested protocol.
|
||||
* The tags can be anything, as long as it's of a type conforming to `Equatable`. `DependencyContainer` is a generic class which takes that type as generic parameter (so one `DependencyContainer` will be tied with a given tag type)
|
||||
* The tags can be StringLiteralType or IntegerLiteralType. That said you can use plain strings or integers as tags.
|
||||
|
||||
|
||||
### Example
|
||||
```swift
|
||||
enum WebService: String {
|
||||
case PersonWS
|
||||
case StarshipWS
|
||||
var tag: Tag { return Tag.String(self.rawValue) }
|
||||
}
|
||||
|
||||
let wsDependencies = DependencyContainer<WebService>() { dip in
|
||||
dip.register(WebService.PersonWS.tag, instance: URLSessionNetworkLayer(baseURL: "http://prod.myapi.com/api/")! as NetworkLayer)
|
||||
dip.register(WebService.StashipWS.tag, instance: URLSessionNetworkLayer(baseURL: "http://dev.myapi.com/api/")! as NetworkLayer)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Concrete Example
|
||||
|
||||
Somewhere in your App target, register the dependencies:
|
||||
|
||||
```swift
|
||||
let dip: DependencyContainer<String> = {
|
||||
let dip = DependencyContainer<String>()
|
||||
let dip: DependencyContainer = {
|
||||
let dip = DependencyContainer()
|
||||
let env = ProductionEnvironment(analytics: true)
|
||||
dip.register(instance: env as EnvironmentType)
|
||||
dip.register(instance: WebService() as WebServiceType)
|
||||
@@ -111,7 +150,7 @@ This way, when running your app target:
|
||||
|
||||
But when running your Unit tests target, it will probably resolve to other instances, depending on how you registered your dependencies in your Test Case.
|
||||
|
||||
### Complete Example
|
||||
### Complete Example Project
|
||||
|
||||
You can find a complete example in the `Example/DipSampleApp` project provided in this repository.
|
||||
|
||||
@@ -122,10 +161,12 @@ network layer returning fixture data during the Unit Tests.
|
||||
This sample uses the Star Wars API provided by swapi.co to fetch Star Wars characters and starships info and display them in TableViews.
|
||||
|
||||
|
||||
## Author
|
||||
## Credits
|
||||
|
||||
Olivier Halligon, olivier@halligon.net
|
||||
This library is authored by **Olivier Halligon**, olivier@halligon.net
|
||||
|
||||
## License
|
||||
**Dip** is available under the **MIT license**. See the `LICENSE` file for more info.
|
||||
|
||||
Dip is available under the MIT license. See the LICENSE file for more info.
|
||||
The animated GIF at the top of this `README.md` is from [this recipe](http://www.kevinandamanda.com/recipes/appetizer/homemade-soft-cinnamon-sugar-pretzel-bites-with-salted-caramel-dipping-sauce.html) on the yummy blog of [Kevin & Amanda](http://www.kevinandamanda.com/recipes/). Go try the recipe!
|
||||
|
||||
The image used as the SampleApp LaunchScreen and Icon is from [Matthew Hine](https://commons.wikimedia.org/wiki/File:Chocolate_con_churros_-_San_Ginés,_Madrid.jpg) and is under _CC-by-2.0_.
|
||||
|
||||
+176
-130
@@ -8,146 +8,192 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* Internal representation of a key to associate protocols & tags to an instance factory
|
||||
*/
|
||||
private struct ProtoTagKey<TagType : Equatable> : Hashable, Equatable, CustomDebugStringConvertible {
|
||||
var protocolType: Any.Type
|
||||
var associatedTag: TagType?
|
||||
|
||||
var hashValue: Int {
|
||||
return "\(protocolType)-\(associatedTag)".hashValue
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
return "type: \(protocolType), tag: \(associatedTag)"
|
||||
}
|
||||
}
|
||||
|
||||
private func ==<T>(lhs: ProtoTagKey<T>, rhs: ProtoTagKey<T>) -> Bool {
|
||||
return lhs.protocolType == rhs.protocolType && lhs.associatedTag == rhs.associatedTag
|
||||
}
|
||||
|
||||
// MARK: - DependencyContainer
|
||||
|
||||
/**
|
||||
_Dip_'s Dependency Containers allow you to do very simple **Dependency Injection**
|
||||
by associating `protocols` to concrete implementations
|
||||
*/
|
||||
public class DependencyContainer<TagType : Equatable> {
|
||||
private typealias InstanceType = Any
|
||||
private typealias InstanceFactory = TagType?->InstanceType
|
||||
private typealias Key = ProtoTagKey<TagType>
|
||||
|
||||
private var dependencies = [Key : InstanceFactory]()
|
||||
private var lock: OSSpinLock = OS_SPINLOCK_INIT
|
||||
|
||||
/**
|
||||
Designated initializer for a DependencyContainer
|
||||
|
||||
- parameter configBlock: A configuration block in which you typically put all you `register` calls.
|
||||
* _Dip_'s Dependency Containers allow you to do very simple **Dependency Injection**
|
||||
* by associating `protocols` to concrete implementations
|
||||
*/
|
||||
public class DependencyContainer {
|
||||
|
||||
/**
|
||||
Use a tag in case you need to register multiple instances or factories
|
||||
with the same protocol, to differentiate them. Tags can be either String
|
||||
or Int, to your convenience.
|
||||
*/
|
||||
public enum Tag: Equatable {
|
||||
case String(StringLiteralType)
|
||||
case Int(IntegerLiteralType)
|
||||
}
|
||||
|
||||
- note: The `configBlock` is simply called at the end of the `init` to let you configure everything.
|
||||
It is only present for convenience to have a cleaner syntax when declaring and initializing
|
||||
your `DependencyContainer` instances.
|
||||
/**
|
||||
* Internal representation of a key to associate protocols & tags to an instance factory
|
||||
*/
|
||||
private struct LookupKey : Hashable, Equatable, CustomDebugStringConvertible {
|
||||
var protocolType: Any.Type
|
||||
var associatedTag: Tag?
|
||||
|
||||
- returns: A new DependencyContainer
|
||||
*/
|
||||
public init(@noescape configBlock: (DependencyContainer->Void) = { _ in }) {
|
||||
configBlock(self)
|
||||
var hashValue: Int {
|
||||
return "\(protocolType)-\(associatedTag)".hashValue
|
||||
}
|
||||
|
||||
// MARK: - Reset all dependencies
|
||||
|
||||
/**
|
||||
Clear all the previously registered dependencies on this container
|
||||
*/
|
||||
public func reset() {
|
||||
lockAndDo {
|
||||
dependencies.removeAll()
|
||||
}
|
||||
var debugDescription: String {
|
||||
return "type: \(protocolType), tag: \(associatedTag)"
|
||||
}
|
||||
|
||||
// MARK: Register dependencies
|
||||
|
||||
/**
|
||||
Register a `TagType?->T` factory (which takes the tag as parameter) with a given tag
|
||||
|
||||
- parameter tag: The arbitrary tag to associate this factory with when registering with that protocol. `nil` to associate with any tag.
|
||||
- parameter factory: The factory to register, typed/casted as the protocol you want to register it as
|
||||
|
||||
- note: You must cast the factory return type to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
|
||||
*/
|
||||
public func register<T : Any>(tag: TagType? = nil, factory: TagType?->T) {
|
||||
let key = Key(protocolType: T.self, associatedTag: tag)
|
||||
lockAndDo {
|
||||
dependencies[key] = { factory($0) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Register a Void->T factory (which don't care about the tag used)
|
||||
|
||||
- parameter tag: The arbitrary tag to associate this factory with when registering with that protocol. `nil` to associate with any tag.
|
||||
- parameter factory: The factory to register, typed/casted as the protocol you want to register it as
|
||||
|
||||
- note: You must cast the factory return type to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
|
||||
*/
|
||||
public func register<T : Any>(tag: TagType? = nil, factory: Void->T) {
|
||||
let key = Key(protocolType: T.self, associatedTag: tag)
|
||||
lockAndDo {
|
||||
dependencies[key] = { _ in factory() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Register a Singleton instance
|
||||
|
||||
|
||||
- parameter tag: The arbitrary tag to associate this instance with when registering with that protocol. `nil` to associate with any tag.
|
||||
- parameter instance: The instance to register, typed/casted as the protocol you want to register it as
|
||||
|
||||
- note: You must cast the instance to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
|
||||
*/
|
||||
public func register<T : Any>(tag: TagType? = nil, @autoclosure(escaping) instance factory: Void->T) {
|
||||
let key = Key(protocolType: T.self, associatedTag: tag)
|
||||
lockAndDo {
|
||||
dependencies[key] = { _ in
|
||||
let instance = factory()
|
||||
self.dependencies[key] = { _ in return instance }
|
||||
return instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Resolve dependencies
|
||||
|
||||
/**
|
||||
Resolve a dependency
|
||||
}
|
||||
|
||||
private typealias InstanceType = Any
|
||||
private typealias InstanceFactory = Tag?->InstanceType
|
||||
private typealias Key = LookupKey
|
||||
|
||||
private var dependencies = [Key : InstanceFactory]()
|
||||
private var lock: OSSpinLock = OS_SPINLOCK_INIT
|
||||
|
||||
// MARK: - Init & Reset
|
||||
|
||||
- parameter tag: The arbitrary tag to look for when resolving this protocol.
|
||||
If no instance/factory was registered with this `tag` for this `protocol`,
|
||||
it will resolve to the instance/factory associated with `nil` (no tag).
|
||||
*/
|
||||
public func resolve<T>(tag: TagType? = nil) -> T! {
|
||||
let key = Key(protocolType: T.self, associatedTag: tag)
|
||||
let nilKey = Key(protocolType: T.self, associatedTag: nil)
|
||||
var resolved: T!
|
||||
lockAndDo { [unowned self] in
|
||||
guard let factory = self.dependencies[key] ?? self.dependencies[nilKey] else {
|
||||
fatalError("No instance factory registered with \(key)")
|
||||
}
|
||||
resolved = factory(tag) as! T
|
||||
}
|
||||
return resolved
|
||||
/**
|
||||
Designated initializer for a DependencyContainer
|
||||
|
||||
- parameter configBlock: A configuration block in which you typically put all you `register` calls.
|
||||
|
||||
- note: The `configBlock` is simply called at the end of the `init` to let you configure everything.
|
||||
It is only present for convenience to have a cleaner syntax when declaring and initializing
|
||||
your `DependencyContainer` instances.
|
||||
|
||||
- returns: A new DependencyContainer
|
||||
*/
|
||||
public init(@noescape configBlock: (DependencyContainer->Void) = { _ in }) {
|
||||
configBlock(self)
|
||||
}
|
||||
|
||||
/**
|
||||
Clear all the previously registered dependencies on this container
|
||||
*/
|
||||
public func reset() {
|
||||
lockAndDo {
|
||||
dependencies.removeAll()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
}
|
||||
|
||||
// MARK: Register dependencies
|
||||
|
||||
/**
|
||||
Register a `TagType?->T` factory (which takes the tag as parameter) with a given tag
|
||||
|
||||
- parameter tag: The arbitrary tag to associate this factory with when registering with that protocol. `nil` to associate with any tag.
|
||||
- parameter factory: The factory to register, typed/casted as the protocol you want to register it as
|
||||
|
||||
- note: You must cast the factory return type to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
|
||||
*/
|
||||
public func register<T>(tag: Tag? = nil, factory: Tag?->T) {
|
||||
let key = Key(protocolType: T.self, associatedTag: tag)
|
||||
lockAndDo {
|
||||
dependencies[key] = { factory($0) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Register a Void->T factory (which don't care about the tag used)
|
||||
|
||||
- parameter tag: The arbitrary tag to associate this factory with when registering with that protocol. `nil` to associate with any tag.
|
||||
- parameter factory: The factory to register, typed/casted as the protocol you want to register it as
|
||||
|
||||
- note: You must cast the factory return type to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
|
||||
*/
|
||||
public func register<T>(tag: Tag? = nil, factory: Void->T) {
|
||||
let key = Key(protocolType: T.self, associatedTag: tag)
|
||||
lockAndDo {
|
||||
dependencies[key] = { _ in factory() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Register a Singleton instance
|
||||
|
||||
|
||||
- parameter tag: The arbitrary tag to associate this instance with when registering with that protocol. `nil` to associate with any tag.
|
||||
- parameter instance: The instance to register, typed/casted as the protocol you want to register it as
|
||||
|
||||
- note: You must cast the instance to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
|
||||
*/
|
||||
public func register<T>(tag: Tag? = nil, @autoclosure(escaping) instance factory: Void->T) {
|
||||
let key = Key(protocolType: T.self, associatedTag: tag)
|
||||
lockAndDo {
|
||||
dependencies[key] = { _ in
|
||||
let instance = factory()
|
||||
self.dependencies[key] = { _ in return instance }
|
||||
return instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Resolve dependencies
|
||||
|
||||
/**
|
||||
Resolve a dependency
|
||||
|
||||
- parameter tag: The arbitrary tag to look for when resolving this protocol.
|
||||
If no instance/factory was registered with this `tag` for this `protocol`,
|
||||
it will resolve to the instance/factory associated with `nil` (no tag).
|
||||
*/
|
||||
public func resolve<T>(tag: Tag? = nil) -> T! {
|
||||
let key = Key(protocolType: T.self, associatedTag: tag)
|
||||
let nilKey = Key(protocolType: T.self, associatedTag: nil)
|
||||
var resolved: T!
|
||||
lockAndDo { [unowned self] in
|
||||
guard let factory = self.dependencies[key] ?? self.dependencies[nilKey] else {
|
||||
fatalError("No instance factory registered with \(key)")
|
||||
}
|
||||
resolved = factory(tag) as! T
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
private func lockAndDo(@noescape block: Void->Void) {
|
||||
OSSpinLockLock(&lock)
|
||||
defer { OSSpinLockUnlock(&lock) }
|
||||
block()
|
||||
}
|
||||
// MARK: - Private Helper
|
||||
|
||||
private func lockAndDo(@noescape block: Void->Void) {
|
||||
OSSpinLockLock(&lock)
|
||||
defer { OSSpinLockUnlock(&lock) }
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Class Extensions
|
||||
|
||||
private func ==(lhs: DependencyContainer.LookupKey, rhs: DependencyContainer.LookupKey) -> Bool {
|
||||
return lhs.protocolType == rhs.protocolType && lhs.associatedTag == rhs.associatedTag
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: IntegerLiteralConvertible {
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self = .Int(value)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: StringLiteralConvertible {
|
||||
public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
|
||||
public typealias UnicodeScalarLiteralType = StringLiteralType
|
||||
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self = .String(value)
|
||||
}
|
||||
|
||||
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
}
|
||||
|
||||
public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.String(lhsString), .String(rhsString)):
|
||||
return lhsString == rhsString
|
||||
case let (.Int(lhsInt), .Int(rhsInt)):
|
||||
return lhsInt == rhsInt
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 400 KiB |
Reference in New Issue
Block a user