Compare commits

...

14 Commits

Author SHA1 Message Date
Olivier Halligon 7f9c54e484 Version Bump: 2.0.0 2015-10-31 15:21:48 +01:00
Olivier Halligon ec177fba78 Rearranged classes in Dip.swift
To make Tag and LookupKey (formerly ProtoTypeKey) inner classes of DependencyContainer
2015-10-31 15:17:25 +01:00
AliSoftware 6b6f85ba4c Merge pull request #3 from ilyapuchka/master
Remove DependencyContainer Generic constraints

See rationale in PR #3
2015-10-31 14:52:41 +01:00
Ilya Puchka 5f845e6701 updated change log 2015-10-30 23:19:15 +01:00
Ilya Puchka 0028083289 updated sample app 2015-10-30 15:30:17 +01:00
Ilya Puchka 10dd5a51a5 Updated readme 2015-10-29 19:50:26 +01:00
Ilya Puchka 4e3a53997e added support for string and integer literal convertible protocols 2015-10-29 19:50:25 +01:00
Ilya Puchka ae8d56e5d8 tag implemented as enum with String and Int cases 2015-10-29 17:01:58 +01:00
Ilya Puchka bc303da493 changed tag back to String 2015-10-23 23:55:44 +02:00
Ilya Puchka 0db3155835 Removed unneeded Any generic constraint 2015-10-23 23:02:18 +02:00
Olivier Halligon 74112c6051 Yummy animated GIF in README ;) 2015-10-11 17:27:41 +02:00
Olivier Halligon a98a9eed56 [README] Using local GIF + Fixing links to source code from the README 2015-10-11 16:25:23 +02:00
Olivier Halligon a4f6db4f8d Bump Version to 1.0.1 — Improved README and Discoverability 2015-10-11 16:11:54 +02:00
Olivier Halligon a74b527324 Better README 2015-10-11 15:43:51 +02:00
21 changed files with 284 additions and 174 deletions
+12
View File
@@ -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
View File
@@ -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)
+2 -2
View File
@@ -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
View File
@@ -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": {
+2 -2
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -9,7 +9,7 @@
import Foundation
import Dip
var wsDependencies = DependencyContainer<WebService>()
var wsDependencies = DependencyContainer()
// MARK: - Mock object used for tests
+4 -3
View File
@@ -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
+55 -14
View File
@@ -5,8 +5,8 @@
[![License](https://img.shields.io/cocoapods/l/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip)
[![Platform](https://img.shields.io/cocoapods/p/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip)
![Chocolate con churros - San Ginés, Madrid](https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Chocolate_con_churros_-_San_Ginés%2C_Madrid.jpg/160px-Chocolate_con_churros_-_San_Ginés%2C_Madrid.jpg)
_Photo by [Matthew Hine](https://commons.wikimedia.org/wiki/File:Chocolate_con_churros_-_San_Ginés,_Madrid.jpg), cc-by-2.0_
![Animated Dipping GIF](cinnamon-pretzels-caramel-dipping.gif)
_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
View File
@@ -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