Compare commits

...

18 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
Olivier Halligon 225fb70bd4 Version bump: 1.0.0 2015-10-11 15:16:55 +02:00
Olivier Halligon 368d4f920f SWAPIStarshipProviderTests 2015-10-11 15:01:31 +02:00
Olivier Halligon 61042efb53 SWAPIPersonProviderTests 2015-10-11 14:26:39 +02:00
Olivier Halligon 4ba6e48fd2 Convert var fetchIDs and fetchOne to functions for readability
+ some code formatting fixes
2015-10-11 05:09:15 +02:00
27 changed files with 580 additions and 308 deletions
+61
View File
@@ -1,7 +1,68 @@
# 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
* Added Unit Tests for `SWAPIPersonProvider` and `SWAPIStarshipProvider`
_All work in progress is now done. I consider `Dip` to be ready for production and with a stable API, hence the `1.0.0` version bump._
#### Example Project
* Using `func fetchIDs` and `func fetchOne` instead of `lazy var` for readability
## 0.1.0
#### Dip
* Dip is now Thread-Safe
* Added a configuration block so we can easily create the container and register the dependencies all in one expression:
```swift
let deps = DependencyContainer() {
$0.register() { x as Foo }
$0.register() { y as Bar }
$0.register() { z as Baz }
}
```
* Source Documentation
#### Example Project
* Code Cleanup
* Added more values to `HardCodedStarshipProvider` so it works when the `PersonProviderAPI` uses real pilots from swapi.co (`SWAPIPersonProvider`)
## 0.0.4
#### Example Project
* Added `SWAPIPersonProvider` & `SWAPIStarshipProvider` that use http://swapi.co
## 0.0.3
#### Example Project
* Revamped the Sample project to a more complete example (using StarWars API!)
* Using Mixins & Traits in the Sample App for `FetchableTrait` and `FillableCell`
## 0.0.2
#### Dip
* Switched from class methods to instance methods ([#1](https://github.com/AliSoftware/Dip/issues/1)). This allows you to have multiple `DependencyContainers`
* Renamed the class from `Dependency` to `DependencyContainer`
* Renamed the `instanceFactory:` parameter to `factory:`
+3 -5
View File
@@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = "Dip"
s.version = "0.1.0"
s.summary = "A simple Dependency Resolver (Simplified Dependency Injection-like 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"
+28 -4
View File
@@ -33,7 +33,17 @@
09D796191BC9BA49003C68EB /* DependencyContainers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D796181BC9BA49003C68EB /* DependencyContainers.swift */; settings = {ASSET_TAGS = (); }; };
09D7961B1BC9BE65003C68EB /* SWAPIStarshipProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D7961A1BC9BE65003C68EB /* SWAPIStarshipProvider.swift */; settings = {ASSET_TAGS = (); }; };
09D7961D1BC9C62E003C68EB /* SWAPICommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D7961C1BC9C62E003C68EB /* SWAPICommon.swift */; settings = {ASSET_TAGS = (); }; };
607FACEC1AFB9204008FA782 /* SWAPIWebServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* SWAPIWebServiceTests.swift */; };
09D796221BCA8305003C68EB /* NetworkLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D796141BC9A5FC003C68EB /* NetworkLayer.swift */; settings = {ASSET_TAGS = (); }; };
09D796231BCA833E003C68EB /* SWAPIPersonProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D796121BC9A5BC003C68EB /* SWAPIPersonProvider.swift */; settings = {ASSET_TAGS = (); }; };
09D796241BCA8345003C68EB /* PersonProviderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900121B1BC6FECA0079C600 /* PersonProviderAPI.swift */; settings = {ASSET_TAGS = (); }; };
09D796251BCA8345003C68EB /* StarshipProviderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900123C1BC7012A0079C600 /* StarshipProviderAPI.swift */; settings = {ASSET_TAGS = (); }; };
09D796261BCA8348003C68EB /* SWAPIStarshipProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D7961A1BC9BE65003C68EB /* SWAPIStarshipProvider.swift */; settings = {ASSET_TAGS = (); }; };
09D796271BCA8550003C68EB /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900123F1BC704C60079C600 /* Person.swift */; settings = {ASSET_TAGS = (); }; };
09D796281BCA8550003C68EB /* Starship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090012411BC7059E0079C600 /* Starship.swift */; settings = {ASSET_TAGS = (); }; };
09D796291BCA86D4003C68EB /* SWAPICommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D7961C1BC9C62E003C68EB /* SWAPICommon.swift */; settings = {ASSET_TAGS = (); }; };
09D7962C1BCA8AD4003C68EB /* NetworkMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D7962A1BCA8AB3003C68EB /* NetworkMock.swift */; settings = {ASSET_TAGS = (); }; };
09D7962F1BCA922E003C68EB /* SWAPIStarshipProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D7962D1BCA91D3003C68EB /* SWAPIStarshipProviderTests.swift */; settings = {ASSET_TAGS = (); }; };
607FACEC1AFB9204008FA782 /* SWAPIPersonProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* SWAPIPersonProviderTests.swift */; };
7BBD849465D99D9D1987AE6D /* Pods_DipTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 304AD039660A2C58EB08D985 /* Pods_DipTests.framework */; };
84D8EBE5B2D583BEFB17C45A /* Pods_DipSampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FE9C70E965FF88C3F20AC76 /* Pods_DipSampleApp.framework */; };
/* End PBXBuildFile section */
@@ -68,11 +78,13 @@
09D796181BC9BA49003C68EB /* DependencyContainers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DependencyContainers.swift; sourceTree = "<group>"; };
09D7961A1BC9BE65003C68EB /* SWAPIStarshipProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SWAPIStarshipProvider.swift; sourceTree = "<group>"; };
09D7961C1BC9C62E003C68EB /* SWAPICommon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SWAPICommon.swift; sourceTree = "<group>"; };
09D7962A1BCA8AB3003C68EB /* NetworkMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkMock.swift; sourceTree = "<group>"; };
09D7962D1BCA91D3003C68EB /* SWAPIStarshipProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SWAPIStarshipProviderTests.swift; sourceTree = "<group>"; };
2FE9C70E965FF88C3F20AC76 /* Pods_DipSampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DipSampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
304AD039660A2C58EB08D985 /* Pods_DipTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DipTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
607FACE51AFB9204008FA782 /* DipTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DipTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
607FACEB1AFB9204008FA782 /* SWAPIWebServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWAPIWebServiceTests.swift; sourceTree = "<group>"; };
607FACEB1AFB9204008FA782 /* SWAPIPersonProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWAPIPersonProviderTests.swift; sourceTree = "<group>"; };
64B6CB26CB93DFD18565BB72 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
6AB71DAFECF410F2FB12A44C /* Pods-DipSampleApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DipSampleApp.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DipSampleApp/Pods-DipSampleApp.debug.xcconfig"; sourceTree = "<group>"; };
7E5EDFB4A9194B50CAED7E1A /* Pods-DipTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DipTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DipTests/Pods-DipTests.debug.xcconfig"; sourceTree = "<group>"; };
@@ -225,7 +237,9 @@
607FACE81AFB9204008FA782 /* Tests */ = {
isa = PBXGroup;
children = (
607FACEB1AFB9204008FA782 /* SWAPIWebServiceTests.swift */,
09D7962A1BCA8AB3003C68EB /* NetworkMock.swift */,
607FACEB1AFB9204008FA782 /* SWAPIPersonProviderTests.swift */,
09D7962D1BCA91D3003C68EB /* SWAPIStarshipProviderTests.swift */,
607FACE91AFB9204008FA782 /* Supporting Files */,
);
path = Tests;
@@ -489,7 +503,17 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
607FACEC1AFB9204008FA782 /* SWAPIWebServiceTests.swift in Sources */,
09D796291BCA86D4003C68EB /* SWAPICommon.swift in Sources */,
09D796221BCA8305003C68EB /* NetworkLayer.swift in Sources */,
09D796231BCA833E003C68EB /* SWAPIPersonProvider.swift in Sources */,
09D796251BCA8345003C68EB /* StarshipProviderAPI.swift in Sources */,
09D7962C1BCA8AD4003C68EB /* NetworkMock.swift in Sources */,
09D7962F1BCA922E003C68EB /* SWAPIStarshipProviderTests.swift in Sources */,
09D796241BCA8345003C68EB /* PersonProviderAPI.swift in Sources */,
09D796271BCA8550003C68EB /* Person.swift in Sources */,
607FACEC1AFB9204008FA782 /* SWAPIPersonProviderTests.swift in Sources */,
09D796261BCA8348003C68EB /* SWAPIStarshipProvider.swift in Sources */,
09D796281BCA8550003C68EB /* Starship.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+2 -2
View File
@@ -19,10 +19,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if let tabBarVC = self.window?.rootViewController as? UITabBarController,
let vcs = tabBarVC.viewControllers as? [UINavigationController] {
if let personListVC = vcs[0].topViewController as? PersonListViewController {
personListVC.fetchAllObjects()
personListVC.loadFirstPage()
}
if let starshipListVC = vcs[1].topViewController as? StarshipListViewController {
starshipListVC.fetchAllObjects()
starshipListVC.loadFirstPage()
}
}
@@ -17,15 +17,9 @@ private let FAKE_STARSHIPS = false
/* ---- */
enum WebService {
case PersonWS
case StarshipWS
}
// 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)
@@ -33,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 {
@@ -21,9 +21,13 @@ enum NetworkResponse {
}
}
func json() throws -> AnyObject {
func json<T>() throws -> T {
let (data, _) = try self.unwrap()
return try NSJSONSerialization.JSONObjectWithData(data, options: [])
let obj = try NSJSONSerialization.JSONObjectWithData(data, options: [])
guard let json = obj as? T else {
throw SWAPIError.InvalidJSON
}
return json
}
}
@@ -12,10 +12,13 @@ class PlistPersonProvider : PersonProviderAPI {
let people: [Person]
init(plist basename: String) {
guard let path = NSBundle.mainBundle().pathForResource(basename, ofType: "plist"),
guard
let path = NSBundle.mainBundle().pathForResource(basename, ofType: "plist"),
let list = NSArray(contentsOfFile: path),
peopleDict = list as? [[String:AnyObject]]
else { fatalError("PLIST for \(basename) not found") }
else {
fatalError("PLIST for \(basename) not found")
}
self.people = peopleDict.map(PlistPersonProvider.personFromDict)
}
@@ -41,7 +44,8 @@ class PlistPersonProvider : PersonProviderAPI {
eyeColor = dict["eyeColor"] as? String,
genderStr = dict["gender"] as? String,
starshipsIDs = dict["starships"] as? [Int]
else { fatalError("Invalid Plist")
else {
fatalError("Invalid Plist")
}
return Person(
@@ -7,11 +7,18 @@
//
import Foundation
import Dip
enum SWAPIError: ErrorType {
case InvalidJSON
}
enum WebService: String {
case PersonWS
case StarshipWS
var tag: DependencyContainer.Tag { return DependencyContainer.Tag.String(self.rawValue) }
}
func idFromURLString(urlString: String) -> Int? {
let url = NSURL(string: urlString)
let idString = url.flatMap { $0.lastPathComponent }
@@ -9,12 +9,12 @@
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
do {
let dict = try response.json()
let dict = try response.json() as NSDictionary
guard let results = dict["results"] as? [NSDictionary] else { throw SWAPIError.InvalidJSON }
// Extract URLs (flatten to ignore invalid ones)
@@ -32,15 +32,15 @@ struct SWAPIPersonProvider : PersonProviderAPI {
func fetch(id: Int, completion: Person? -> Void) {
ws.request("people/\(id)") { response in
do {
let json = try response.json()
guard let dict = json as? NSDictionary,
let name = dict["name"] as? String,
let heightStr = dict["height"] as? String, height = Int(heightStr),
let massStr = dict["mass"] as? String, mass = Int(massStr),
let hairColor = dict["hair_color"] as? String,
let eyeColor = dict["eye_color"] as? String,
let gender = dict["gender"] as? String,
let starshipURLStrings = dict["starships"] as? [String]
let json = try response.json() as NSDictionary
guard
let name = json["name"] as? String,
let heightStr = json["height"] as? String, height = Int(heightStr),
let massStr = json["mass"] as? String, mass = Int(massStr),
let hairColor = json["hair_color"] as? String,
let eyeColor = json["eye_color"] as? String,
let gender = json["gender"] as? String,
let starshipURLStrings = json["starships"] as? [String]
else {
throw SWAPIError.InvalidJSON
}
@@ -9,12 +9,12 @@
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
do {
let dict = try response.json()
let dict = try response.json() as NSDictionary
guard let results = dict["results"] as? [NSDictionary] else { throw SWAPIError.InvalidJSON }
// Extract URLs (flatten to ignore invalid ones)
@@ -32,14 +32,14 @@ struct SWAPIStarshipProvider : StarshipProviderAPI {
func fetch(id: Int, completion: Starship? -> Void) {
ws.request("starships/\(id)") { response in
do {
let json = try response.json()
guard let dict = json as? NSDictionary,
let name = dict["name"] as? String,
let model = dict["model"] as? String,
let manufacturer = dict["manufacturer"] as? String,
let crewStr = dict["crew"] as? String, crew = Int(crewStr),
let passengersStr = dict["passengers"] as? String, passengers = Int(passengersStr),
let pilotIDStrings = dict["pilots"] as? [String]
let json = try response.json() as NSDictionary
guard
let name = json["name"] as? String,
let model = json["model"] as? String,
let manufacturer = json["manufacturer"] as? String,
let crewStr = json["crew"] as? String, crew = Int(crewStr),
let passengersStr = json["passengers"] as? String, passengers = Int(passengersStr),
let pilotIDStrings = json["pilots"] as? [String]
else {
throw SWAPIError.InvalidJSON
}
@@ -14,13 +14,13 @@ protocol FetchableTrait: class {
var batchRequestID: Int { get set }
var tableView: UITableView! { get }
var fetchIDs: ([Int] -> Void) -> Void { get }
var fetchOne: (Int, ObjectType? -> Void) -> Void { get }
func fetchIDs(completion: [Int] -> Void)
func fetchOne(id: Int, completion: ObjectType? -> Void)
var fetchProgress: (current: Int, total: Int?) { get set }
}
extension FetchableTrait {
func fetchObjects(objectIDs: [Int]) {
func loadObjects(objectIDs: [Int]) {
self.batchRequestID += 1
let batch = self.batchRequestID
@@ -34,19 +34,19 @@ extension FetchableTrait {
if self.objects == nil { self.objects = [] }
self.objects?.append(object)
self.fetchProgress = (self.objects?.count ?? 0, objectIDs.count)
self.fetchProgress.current = self.objects?.count ?? 0
self.tableView?.reloadData()
}
}
}
func fetchAllObjects() {
func loadFirstPage() {
self.batchRequestID += 1
let batch = self.batchRequestID
fetchProgress = (0, nil)
fetchIDs() { objectIDs in
guard batch == self.batchRequestID else { return }
self.fetchObjects(objectIDs)
self.loadObjects(objectIDs)
}
}
@@ -12,9 +12,12 @@ class PersonListViewController: UITableViewController, FetchableTrait {
var objects: [Person]?
var batchRequestID = 0
lazy var fetchIDs: ([Int] -> Void) -> Void = (providerDependencies.resolve() as PersonProviderAPI).fetchIDs
lazy var fetchOne: (Int, Person? -> Void) -> Void = { personID, completion in
let provider = providerDependencies.resolve(personID) as PersonProviderAPI
func fetchIDs(completion: [Int] -> Void) {
let provider = providerDependencies.resolve() as PersonProviderAPI
return provider.fetchIDs(completion)
}
func fetchOne(personID: Int, completion: Person? -> Void) {
let provider = providerDependencies.resolve(.Int(personID)) as PersonProviderAPI
return provider.fetch(personID, completion: completion)
}
@@ -35,7 +38,7 @@ class PersonListViewController: UITableViewController, FetchableTrait {
fatalError()
}
destVC.fetchObjects(person.starshipIDs)
destVC.loadObjects(person.starshipIDs)
}
}
@@ -7,15 +7,21 @@
//
import UIKit
import Dip
class StarshipListViewController : UITableViewController, FetchableTrait {
var objects: [Starship]?
var batchRequestID = 0
var provider: (Int? -> StarshipProviderAPI) = { providerDependencies.resolve($0) }
lazy var fetchIDs: ([Int] -> Void) -> Void = self.provider(nil).fetchIDs
lazy var fetchOne: (Int, Starship? -> Void) -> Void = { shipID, completion in
self.provider(shipID).fetch(shipID, completion: completion)
private func provider(tag:Int?) -> StarshipProviderAPI {
return providerDependencies.resolve(tag.flatMap { .Int($0) })
}
func fetchIDs(completion: [Int] -> Void) {
provider(nil).fetchIDs(completion)
}
func fetchOne(shipID:Int, completion: Starship? -> Void) {
provider(shipID).fetch(shipID, completion: completion)
}
var fetchProgress: (current: Int, total: Int?) = (0, nil) {
@@ -35,7 +41,7 @@ class StarshipListViewController : UITableViewController, FetchableTrait {
fatalError()
}
destVC.fetchObjects(starship.pilotIDs)
destVC.loadObjects(starship.pilotIDs)
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
PODS:
- Dip (0.1.0)
- Dip (2.0.0)
DEPENDENCIES:
- Dip (from `../`)
@@ -9,6 +9,6 @@ EXTERNAL SOURCES:
:path: ../
SPEC CHECKSUMS:
Dip: c6d545af478b84d3708bf02d986fe687cb3322cc
Dip: ae784a32e584d5c57809b2cdc4fd2f287d075f7a
COCOAPODS: 0.39.0
+4 -4
View File
@@ -1,8 +1,8 @@
{
"name": "Dip",
"version": "0.1.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": "0.1.0"
"tag": "2.0.0"
},
"social_media_url": "https://twitter.com/aligatr",
"platforms": {
+2 -2
View File
@@ -1,5 +1,5 @@
PODS:
- Dip (0.1.0)
- Dip (2.0.0)
DEPENDENCIES:
- Dip (from `../`)
@@ -9,6 +9,6 @@ EXTERNAL SOURCES:
:path: ../
SPEC CHECKSUMS:
Dip: c6d545af478b84d3708bf02d986fe687cb3322cc
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 = '63F05AA5C776F961D2D8222C'
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>0.1.0</string>
<string>2.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+37
View File
@@ -0,0 +1,37 @@
//
// NetworkMock.swift
// Dip
//
// Created by Olivier Halligon on 11/10/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
import Dip
var wsDependencies = DependencyContainer()
// MARK: - Mock object used for tests
struct NetworkMock : NetworkLayer {
let fakeData: NSData?
init(json: AnyObject) {
do {
fakeData = try NSJSONSerialization.dataWithJSONObject(json, options: [])
} catch {
fakeData = nil
}
}
func request(path: String, completion: NetworkResponse -> Void) {
let fakeURL = NSURL(string: "stub://")!.URLByAppendingPathComponent(path)
if let data = fakeData {
let response = NSHTTPURLResponse(URL: fakeURL, statusCode: 200, HTTPVersion: "1.1", headerFields:nil)!
completion(.Success(data, response))
} else {
let response = NSHTTPURLResponse(URL: fakeURL, statusCode: 204, HTTPVersion: "1.1", headerFields:nil)!
completion(.Success(NSData(), response))
}
}
}
@@ -0,0 +1,68 @@
//
// SWAPIPersonProviderTests.swift
// Dip
//
// Created by Olivier Halligon on 11/10/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import XCTest
import Dip
class SWAPIPersonProviderTests: XCTestCase {
let fakePerson1 = ["name": "John Doe", "mass": "72", "height": "172", "eye_color": "brown", "hair_color": "black", "gender": "male",
"starships": ["stub://starship/7/", "stub://starship/15"], "url": "stub://people/1"]
let fakePerson2 = ["name": "Jane Doe", "mass": "63", "height": "167", "eye_color": "blue", "hair_color": "red", "gender": "female",
"starships": ["stub://starship/11/"], "url": "stub://people/12"]
override func setUp() {
super.setUp()
wsDependencies.reset()
}
func testFetchPersonIDs() {
let mock = NetworkMock(json: ["results": [fakePerson1, fakePerson2]])
wsDependencies.register(WebService.PersonWS.tag, instance: mock as NetworkLayer)
let provider = SWAPIPersonProvider()
provider.fetchIDs { personIDs in
XCTAssertNotNil(personIDs)
XCTAssertEqual(personIDs.count, 2)
XCTAssertEqual(personIDs[0], 1)
XCTAssertEqual(personIDs[1], 12)
}
}
func testFetchOnePerson() {
let mock = NetworkMock(json: fakePerson1)
wsDependencies.register(WebService.PersonWS.tag, instance: mock as NetworkLayer)
let provider = SWAPIPersonProvider()
provider.fetch(1) { person in
XCTAssertNotNil(person)
XCTAssertEqual(person?.name, "John Doe")
XCTAssertEqual(person?.mass, 72)
XCTAssertEqual(person?.height, 172)
XCTAssertEqual(person?.eyeColor, "brown")
XCTAssertEqual(person?.hairColor, "black")
XCTAssertEqual(person?.gender, .Male)
XCTAssertEqual(person?.starshipIDs.count, 2)
XCTAssertEqual(person?.starshipIDs[0], 7)
XCTAssertEqual(person?.starshipIDs[1], 15)
}
}
func testFetchInvalidPerson() {
let json = ["error":"whoops"]
let mock = NetworkMock(json: json)
wsDependencies.register(WebService.PersonWS.tag, instance: mock as NetworkLayer)
let provider = SWAPIPersonProvider()
provider.fetch(12) { person in
XCTAssertNil(person)
}
}
}
@@ -0,0 +1,67 @@
//
// SWAPIStarshipProviderTests.swift
// Dip
//
// Created by Olivier Halligon on 11/10/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import XCTest
import Dip
class SWAPIStarshipProviderTests: XCTestCase {
let fakeShip1 = ["name": "Falcon", "model": "Fighter", "manufacturer": "Fake Industries", "crew": "7", "passengers": "15",
"pilots": ["stub://people/1/", "stub://people/9"], "url": "stub://starship/4"]
let fakeShip2 = ["name": "Voyager", "model": "Cargo", "manufacturer": "Fake Industries", "crew": "18", "passengers": "150",
"pilots": ["stub://people/2/", "stub://people/3"], "url": "stub://starship/31"]
override func setUp() {
super.setUp()
wsDependencies.reset()
}
func testFetchStarshipIDs() {
let mock = NetworkMock(json: ["results": [fakeShip1, fakeShip2]])
wsDependencies.register(WebService.StarshipWS.tag, instance: mock as NetworkLayer)
let provider = SWAPIStarshipProvider()
provider.fetchIDs { shipIDs in
XCTAssertNotNil(shipIDs)
XCTAssertEqual(shipIDs.count, 2)
XCTAssertEqual(shipIDs[0], 4)
XCTAssertEqual(shipIDs[1], 31)
}
}
func testFetchOneStarship() {
let mock = NetworkMock(json: fakeShip1)
wsDependencies.register(WebService.StarshipWS.tag, instance: mock as NetworkLayer)
let provider = SWAPIStarshipProvider()
provider.fetch(1) { starship in
XCTAssertNotNil(starship)
XCTAssertEqual(starship?.name, "Falcon")
XCTAssertEqual(starship?.model, "Fighter")
XCTAssertEqual(starship?.manufacturer, "Fake Industries")
XCTAssertEqual(starship?.crew, 7)
XCTAssertEqual(starship?.passengers, 15)
XCTAssertNotNil(starship?.pilotIDs)
XCTAssertEqual(starship?.pilotIDs[0], 1)
XCTAssertEqual(starship?.pilotIDs[1], 9)
}
}
func testFetchInvalidStarship() {
let json = ["error":"whoops"]
let mock = NetworkMock(json: json)
wsDependencies.register(WebService.StarshipWS.tag, instance: mock as NetworkLayer)
let provider = SWAPIStarshipProvider()
provider.fetch(12) { starship in
XCTAssertNil(starship)
}
}
}
-89
View File
@@ -1,89 +0,0 @@
import UIKit
import XCTest
import Dip
var dip = DependencyContainer<String>()
//let p1 = ["name": "John Doe", "mass": "72", "height": "172", "eye_color": "brown", "hair_color": "black"]
//let p2 = ["name": "Jane Doe", "mass": "63", "height": "167", "eye_color": "blue", "hair_color": "red"]
//
//class SWAPIWebServiceTests: XCTestCase {
//
// // MARK: - Mock object used for tests
//
// struct NetworkMock : NetworkLayer {
// let fakeData: NSData?
//
// init(json: AnyObject) {
// do {
// fakeData = try NSJSONSerialization.dataWithJSONObject(json, options: [])
// } catch {
// fakeData = nil
// }
// }
//
// func fetchURL(url: NSURL, completion: NSData? -> Void) {
// completion(fakeData)
// }
// }
//
// // MARK: - Test Suite
//
// override func setUp() {
// super.setUp()
//
// dip.reset()
// dip.register(instance: SWAPIPersonFactory() as PersonFactoryAPI)
// dip.register(instance: JSONSerializer() as SerializerAPI)
// }
//
// func testFetchPersons() {
// let mock = NetworkMock(json: ["results":[p1,p2]])
// dip.register(instance: mock as NetworkLayer)
//
// let ws = SWAPIWebService()
// ws.fetch { persons in
// XCTAssertNotNil(persons)
// XCTAssertEqual(persons?.count, 2)
//
// XCTAssertEqual(persons?[0].name, "John Doe")
// XCTAssertEqual(persons?[0].mass, 72)
// XCTAssertEqual(persons?[0].height, 172)
// XCTAssertEqual(persons?[0].eyesColor, "brown")
// XCTAssertEqual(persons?[0].hairColor, "black")
//
// XCTAssertEqual(persons?[1].name, "Jane Doe")
// XCTAssertEqual(persons?[1].mass, 63)
// XCTAssertEqual(persons?[1].height, 167)
// XCTAssertEqual(persons?[1].eyesColor, "blue")
// XCTAssertEqual(persons?[1].hairColor, "red")
// }
// }
//
// func testFetchOnePerson() {
// let mock = NetworkMock(json: p1)
// dip.register(instance: mock as NetworkLayer)
//
// let ws = SWAPIWebService()
// ws.fetch(1) { person in
// XCTAssertNotNil(person)
// XCTAssertEqual(person?.name, "John Doe")
// XCTAssertEqual(person?.mass, 72)
// XCTAssertEqual(person?.height, 172)
// XCTAssertEqual(person?.eyesColor, "brown")
// XCTAssertEqual(person?.hairColor, "black")
// }
// }
//
// func testFetchInvalidPerson() {
// let json = ["error":"whoops"]
// let mock = NetworkMock(json: json)
// dip.register(instance: mock as NetworkLayer)
//
// let ws = SWAPIWebService()
// ws.fetch(12) { person in
// XCTAssertNil(person)
// }
// }
//}
+54 -18
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,15 +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.
## Work In Progress
## Credits
* [ ] Unit Tests
This library is authored by **Olivier Halligon**, olivier@halligon.net
**Dip** is available under the **MIT license**. See the `LICENSE` file for more info.
## Author
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!
Olivier Halligon, olivier@halligon.net
## License
Dip is available under the MIT license. See the LICENSE file for more info.
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