Compare commits

...

8 Commits

Author SHA1 Message Date
Olivier Halligon cbf63cf044 README: Fix links to code in example project 2015-10-06 00:05:19 +02:00
Olivier Halligon de4cbcc7ed Adding NetworkLayer protocol + 1st Unit Test
+ reorganized files into subdirectories
2015-10-06 00:01:43 +02:00
Olivier Halligon d295b6909c CHANGELOG 2015-10-05 22:57:49 +02:00
Olivier Halligon 56e4250eac Made the Tag generic so we can use anything (and not just Strings anymore), as long as it's Equatable 2015-10-05 22:54:13 +02:00
Olivier Halligon 15bc776a0a Merge branch 'dip-instance-issue-1' 2015-10-05 22:45:09 +02:00
Olivier Halligon 16e68a0e1c Update README with the new way using instance methods 2015-10-05 22:44:33 +02:00
Olivier Halligon 61a2bacd73 Migrate class methods to instance methods + renamed as DependencyContainer 2015-10-05 20:58:16 +02:00
Olivier Halligon dd3dd2cf9b [Sample App] Cleaner API & code 2015-10-04 20:27:40 +02:00
21 changed files with 340 additions and 183 deletions
+7
View File
@@ -1,5 +1,12 @@
# CHANGELOG
## 0.0.2
* 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:`
* Made the `DependencyContainer` generic of the type of _tag_. We are no longer limited to tags of type `String`, we can now use anything that's `Equatable`.
## 0.0.1
Initial version to release the early proof of concept.
+52 -16
View File
@@ -10,19 +10,34 @@
097D52E81BC13B0D006C893C /* WebServiceAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52E71BC13B0D006C893C /* WebServiceAPI.swift */; settings = {ASSET_TAGS = (); }; };
097D52EA1BC15FFF006C893C /* PersonFactoryAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52E91BC15FFF006C893C /* PersonFactoryAPI.swift */; settings = {ASSET_TAGS = (); }; };
097D52ED1BC16091006C893C /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52EC1BC16091006C893C /* Person.swift */; settings = {ASSET_TAGS = (); }; };
097D52EF1BC1611C006C893C /* StarWarsWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52EE1BC1611C006C893C /* StarWarsWS.swift */; settings = {ASSET_TAGS = (); }; };
097D52EF1BC1611C006C893C /* SWAPIWebService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52EE1BC1611C006C893C /* SWAPIWebService.swift */; settings = {ASSET_TAGS = (); }; };
097D52F11BC161F7006C893C /* SerializerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52F01BC161F7006C893C /* SerializerAPI.swift */; settings = {ASSET_TAGS = (); }; };
097D52F51BC166F3006C893C /* JSONSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52F41BC166F3006C893C /* JSONSerializer.swift */; settings = {ASSET_TAGS = (); }; };
097D52F71BC169C0006C893C /* StarWarsPersonFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52F61BC169C0006C893C /* StarWarsPersonFactory.swift */; settings = {ASSET_TAGS = (); }; };
097D52F71BC169C0006C893C /* SWAPIPersonFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52F61BC169C0006C893C /* SWAPIPersonFactory.swift */; settings = {ASSET_TAGS = (); }; };
097D52F91BC17418006C893C /* PersonFormatterAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52F81BC17418006C893C /* PersonFormatterAPI.swift */; settings = {ASSET_TAGS = (); }; };
097D52FB1BC1745B006C893C /* MassHeightFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52FA1BC1745B006C893C /* MassHeightFormatter.swift */; settings = {ASSET_TAGS = (); }; };
097D52FD1BC174B6006C893C /* EyesHairFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52FC1BC174B6006C893C /* EyesHairFormatter.swift */; settings = {ASSET_TAGS = (); }; };
097D53011BC31F4A006C893C /* Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D53001BC31F4A006C893C /* Tags.swift */; settings = {ASSET_TAGS = (); }; };
097D53021BC31FA6006C893C /* WebServiceAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52E71BC13B0D006C893C /* WebServiceAPI.swift */; settings = {ASSET_TAGS = (); }; };
097D53031BC31FA6006C893C /* SerializerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52F01BC161F7006C893C /* SerializerAPI.swift */; settings = {ASSET_TAGS = (); }; };
097D53041BC31FA6006C893C /* PersonFactoryAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52E91BC15FFF006C893C /* PersonFactoryAPI.swift */; settings = {ASSET_TAGS = (); }; };
097D53051BC31FA6006C893C /* PersonFormatterAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52F81BC17418006C893C /* PersonFormatterAPI.swift */; settings = {ASSET_TAGS = (); }; };
097D53061BC31FAE006C893C /* SWAPIWebService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52EE1BC1611C006C893C /* SWAPIWebService.swift */; settings = {ASSET_TAGS = (); }; };
097D53071BC31FC5006C893C /* Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D53001BC31F4A006C893C /* Tags.swift */; settings = {ASSET_TAGS = (); }; };
097D53081BC32053006C893C /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52EC1BC16091006C893C /* Person.swift */; settings = {ASSET_TAGS = (); }; };
097D530A1BC3243D006C893C /* NetworkLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D53091BC3243D006C893C /* NetworkLayer.swift */; settings = {ASSET_TAGS = (); }; };
097D530C1BC324DA006C893C /* NSURLSessionNetworkLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D530B1BC324DA006C893C /* NSURLSessionNetworkLayer.swift */; settings = {ASSET_TAGS = (); }; };
097D530D1BC3250B006C893C /* SWAPIPersonFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52F61BC169C0006C893C /* SWAPIPersonFactory.swift */; settings = {ASSET_TAGS = (); }; };
097D530E1BC3250E006C893C /* JSONSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52F41BC166F3006C893C /* JSONSerializer.swift */; settings = {ASSET_TAGS = (); }; };
097D530F1BC3250E006C893C /* MassHeightFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52FA1BC1745B006C893C /* MassHeightFormatter.swift */; settings = {ASSET_TAGS = (); }; };
097D53101BC3250E006C893C /* EyesHairFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D52FC1BC174B6006C893C /* EyesHairFormatter.swift */; settings = {ASSET_TAGS = (); }; };
097D53111BC32513006C893C /* NetworkLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D53091BC3243D006C893C /* NetworkLayer.swift */; settings = {ASSET_TAGS = (); }; };
099022621BC123C000E76F43 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099022611BC123C000E76F43 /* AppDelegate.swift */; };
099022641BC123C000E76F43 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099022631BC123C000E76F43 /* ViewController.swift */; };
099022671BC123C000E76F43 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 099022651BC123C000E76F43 /* Main.storyboard */; };
099022691BC123C000E76F43 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 099022681BC123C000E76F43 /* Assets.xcassets */; };
0990226C1BC123C000E76F43 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0990226A1BC123C000E76F43 /* LaunchScreen.storyboard */; };
607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; };
607FACEC1AFB9204008FA782 /* SWAPIWebServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* SWAPIWebServiceTests.swift */; };
7BBD849465D99D9D1987AE6D /* Pods_DipTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 304AD039660A2C58EB08D985 /* Pods_DipTests.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
84D8EBE5B2D583BEFB17C45A /* Pods_DipSampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FE9C70E965FF88C3F20AC76 /* Pods_DipSampleApp.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
/* End PBXBuildFile section */
@@ -31,14 +46,17 @@
097D52E71BC13B0D006C893C /* WebServiceAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebServiceAPI.swift; sourceTree = "<group>"; };
097D52E91BC15FFF006C893C /* PersonFactoryAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonFactoryAPI.swift; sourceTree = "<group>"; };
097D52EC1BC16091006C893C /* Person.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Person.swift; sourceTree = "<group>"; };
097D52EE1BC1611C006C893C /* StarWarsWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarWarsWS.swift; sourceTree = "<group>"; };
097D52EE1BC1611C006C893C /* SWAPIWebService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SWAPIWebService.swift; sourceTree = "<group>"; };
097D52F01BC161F7006C893C /* SerializerAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SerializerAPI.swift; sourceTree = "<group>"; };
097D52F41BC166F3006C893C /* JSONSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONSerializer.swift; sourceTree = "<group>"; };
097D52F61BC169C0006C893C /* StarWarsPersonFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarWarsPersonFactory.swift; sourceTree = "<group>"; };
097D52F61BC169C0006C893C /* SWAPIPersonFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SWAPIPersonFactory.swift; sourceTree = "<group>"; };
097D52F81BC17418006C893C /* PersonFormatterAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonFormatterAPI.swift; sourceTree = "<group>"; };
097D52FA1BC1745B006C893C /* MassHeightFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MassHeightFormatter.swift; sourceTree = "<group>"; };
097D52FC1BC174B6006C893C /* EyesHairFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EyesHairFormatter.swift; sourceTree = "<group>"; };
097D52FE1BC18A09006C893C /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = "<group>"; };
097D53001BC31F4A006C893C /* Tags.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tags.swift; sourceTree = "<group>"; };
097D53091BC3243D006C893C /* NetworkLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkLayer.swift; sourceTree = "<group>"; };
097D530B1BC324DA006C893C /* NSURLSessionNetworkLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSURLSessionNetworkLayer.swift; sourceTree = "<group>"; };
0990225F1BC123C000E76F43 /* DipSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DipSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
099022611BC123C000E76F43 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
099022631BC123C000E76F43 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
@@ -50,7 +68,7 @@
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 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = "<group>"; };
607FACEB1AFB9204008FA782 /* SWAPIWebServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWAPIWebServiceTests.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>"; };
@@ -83,7 +101,7 @@
097D52E61BC139A8006C893C /* Services */ = {
isa = PBXGroup;
children = (
097D52F21BC16258006C893C /* APIs */,
097D52F21BC16258006C893C /* Protocols */,
097D52F31BC16271006C893C /* Implementations */,
);
path = Services;
@@ -97,27 +115,29 @@
path = Model;
sourceTree = "<group>";
};
097D52F21BC16258006C893C /* APIs */ = {
097D52F21BC16258006C893C /* Protocols */ = {
isa = PBXGroup;
children = (
097D52E71BC13B0D006C893C /* WebServiceAPI.swift */,
097D52F01BC161F7006C893C /* SerializerAPI.swift */,
097D52E91BC15FFF006C893C /* PersonFactoryAPI.swift */,
097D52F81BC17418006C893C /* PersonFormatterAPI.swift */,
097D53091BC3243D006C893C /* NetworkLayer.swift */,
);
name = APIs;
path = Protocols;
sourceTree = "<group>";
};
097D52F31BC16271006C893C /* Implementations */ = {
isa = PBXGroup;
children = (
097D52EE1BC1611C006C893C /* StarWarsWS.swift */,
097D52F61BC169C0006C893C /* StarWarsPersonFactory.swift */,
097D52EE1BC1611C006C893C /* SWAPIWebService.swift */,
097D52F61BC169C0006C893C /* SWAPIPersonFactory.swift */,
097D52F41BC166F3006C893C /* JSONSerializer.swift */,
097D52FA1BC1745B006C893C /* MassHeightFormatter.swift */,
097D52FC1BC174B6006C893C /* EyesHairFormatter.swift */,
097D530B1BC324DA006C893C /* NSURLSessionNetworkLayer.swift */,
);
name = Implementations;
path = Implementations;
sourceTree = "<group>";
};
099022601BC123C000E76F43 /* DipSampleApp */ = {
@@ -125,6 +145,7 @@
children = (
097D52EB1BC16083006C893C /* Model */,
097D52E61BC139A8006C893C /* Services */,
097D53001BC31F4A006C893C /* Tags.swift */,
099022611BC123C000E76F43 /* AppDelegate.swift */,
099022631BC123C000E76F43 /* ViewController.swift */,
099022651BC123C000E76F43 /* Main.storyboard */,
@@ -168,7 +189,7 @@
607FACE81AFB9204008FA782 /* Tests */ = {
isa = PBXGroup;
children = (
607FACEB1AFB9204008FA782 /* Tests.swift */,
607FACEB1AFB9204008FA782 /* SWAPIWebServiceTests.swift */,
607FACE91AFB9204008FA782 /* Supporting Files */,
);
path = Tests;
@@ -402,14 +423,17 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
097D530A1BC3243D006C893C /* NetworkLayer.swift in Sources */,
097D52ED1BC16091006C893C /* Person.swift in Sources */,
097D52F11BC161F7006C893C /* SerializerAPI.swift in Sources */,
097D52F91BC17418006C893C /* PersonFormatterAPI.swift in Sources */,
099022641BC123C000E76F43 /* ViewController.swift in Sources */,
097D530C1BC324DA006C893C /* NSURLSessionNetworkLayer.swift in Sources */,
097D52F51BC166F3006C893C /* JSONSerializer.swift in Sources */,
097D53011BC31F4A006C893C /* Tags.swift in Sources */,
099022621BC123C000E76F43 /* AppDelegate.swift in Sources */,
097D52F71BC169C0006C893C /* StarWarsPersonFactory.swift in Sources */,
097D52EF1BC1611C006C893C /* StarWarsWS.swift in Sources */,
097D52F71BC169C0006C893C /* SWAPIPersonFactory.swift in Sources */,
097D52EF1BC1611C006C893C /* SWAPIWebService.swift in Sources */,
097D52FD1BC174B6006C893C /* EyesHairFormatter.swift in Sources */,
097D52FB1BC1745B006C893C /* MassHeightFormatter.swift in Sources */,
097D52E81BC13B0D006C893C /* WebServiceAPI.swift in Sources */,
@@ -421,7 +445,19 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
607FACEC1AFB9204008FA782 /* Tests.swift in Sources */,
607FACEC1AFB9204008FA782 /* SWAPIWebServiceTests.swift in Sources */,
097D53051BC31FA6006C893C /* PersonFormatterAPI.swift in Sources */,
097D53071BC31FC5006C893C /* Tags.swift in Sources */,
097D53111BC32513006C893C /* NetworkLayer.swift in Sources */,
097D53021BC31FA6006C893C /* WebServiceAPI.swift in Sources */,
097D53041BC31FA6006C893C /* PersonFactoryAPI.swift in Sources */,
097D530E1BC3250E006C893C /* JSONSerializer.swift in Sources */,
097D53031BC31FA6006C893C /* SerializerAPI.swift in Sources */,
097D53081BC32053006C893C /* Person.swift in Sources */,
097D53101BC3250E006C893C /* EyesHairFormatter.swift in Sources */,
097D530F1BC3250E006C893C /* MassHeightFormatter.swift in Sources */,
097D530D1BC3250B006C893C /* SWAPIPersonFactory.swift in Sources */,
097D53061BC31FAE006C893C /* SWAPIWebService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+10 -12
View File
@@ -9,18 +9,16 @@
import UIKit
import Dip
enum PersonFormatterTags : String {
case MassHeight
case EyesHair
}
private let _dependencies: Void = {
Dependency.register(instance: StarWarsWebService() as WebServiceAPI)
Dependency.register(instance: StarWarsJSONPersonFactory() as PersonFactoryAPI)
Dependency.register(instance: JSONSerializer() as SerializerAPI)
Dependency.register(PersonFormatterTags.MassHeight.rawValue, instance: MassHeightFormatter() as PersonFormatterAPI)
Dependency.register(PersonFormatterTags.EyesHair.rawValue, instance: EyesHairFormatter() as PersonFormatterAPI)
}()
let dip: DependencyContainer<PersonFormatterTag> = {
let dip = DependencyContainer<PersonFormatterTag>()
dip.register(instance: NSURLSessionNetworkLayer() as NetworkLayer)
dip.register(instance: SWAPIWebService() as WebServiceAPI)
dip.register(instance: SWAPIPersonFactory() as PersonFactoryAPI)
dip.register(instance: JSONSerializer() as SerializerAPI)
dip.register(.MassHeight, instance: MassHeightFormatter() as PersonFormatterAPI)
dip.register(.EyesHair, instance: EyesHairFormatter() as PersonFormatterAPI)
return dip
}()
@UIApplicationMain
@@ -0,0 +1,22 @@
//
// NSURLSessionNetworkLayer.swift
// Dip
//
// Created by Olivier Halligon on 05/10/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
class NSURLSessionNetworkLayer : NetworkLayer {
let session = NSURLSession.sharedSession()
func fetchURL(url: NSURL, completion: NSData? -> Void) {
let task = session.dataTaskWithURL(url) { (data: NSData?, resp: NSURLResponse?, error: NSError?) -> Void in
dispatch_async(dispatch_get_main_queue()) {
completion(data)
}
}
task.resume()
}
}
@@ -1,5 +1,5 @@
//
// StarWarsJSONPersonFactory.swift
// SWAPIPersonFactory.swift
// Dip
//
// Created by Olivier Halligon on 04/10/2015.
@@ -9,16 +9,16 @@
import Foundation
import Dip
class StarWarsJSONPersonFactory : PersonFactoryAPI {
class SWAPIPersonFactory : PersonFactoryAPI {
typealias JSONDict = [String:AnyObject]
enum Error : ErrorType {
case MissingResultsEntry
case InvalidPersonSchema
}
let serializer = Dependency.resolve() as SerializerAPI
let serializer = dip.resolve() as SerializerAPI
func personListFromData(personData: NSData) throws -> [Person] {
func peopleFromData(personData: NSData) throws -> [Person] {
let json = try serializer.dictionaryFromData(personData)
if let results = json["results"] as? [JSONDict] {
return try results.map { try personFromJSON($0) }
@@ -0,0 +1,31 @@
//
// SWAPIWebService.swift
// Dip
//
// Created by Olivier Halligon on 04/10/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
import Dip
/// WebService for The StarWars API see http://swapi.co/documentation
class SWAPIWebService : WebServiceAPI {
let networkLayer = dip.resolve() as NetworkLayer
let personFactory = dip.resolve() as PersonFactoryAPI
func fetch(completion: [Person]? -> Void) {
let url = NSURL(string: "http://swapi.co/api/people/")!
networkLayer.fetchURLAndMap(url, completion: completion) { data in
return try self.personFactory.peopleFromData(data)
}
}
func fetch(id: Int, completion: Person? -> Void) {
let url = NSURL(string: "http://swapi.co/api/people/\(id)")!
networkLayer.fetchURLAndMap(url, completion: completion) { data in
return try self.personFactory.personFromData(data)
}
}
}
@@ -0,0 +1,32 @@
//
// NetworkLayer.swift
// Dip
//
// Created by Olivier Halligon on 05/10/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
protocol NetworkLayer {
func fetchURL(url: NSURL, completion: NSData? -> Void)
func fetchURLAndMap<T>(url: NSURL, completion: T? -> Void, transform: NSData throws -> T)
}
extension NetworkLayer {
func fetchURLAndMap<T>(url: NSURL, completion: T? -> Void, transform: NSData throws -> T) {
fetchURL(url) { (data: NSData?) -> Void in
guard let data = data else {
completion(nil)
return
}
let result: T?
do {
result = try transform(data)
} catch {
result = nil
}
completion(result)
}
}
}
@@ -9,6 +9,6 @@
import Foundation
protocol PersonFactoryAPI {
func personListFromData(personData: NSData) throws -> [Person]
func peopleFromData(personData: NSData) throws -> [Person]
func personFromData(personData: NSData) throws -> Person
}
@@ -9,6 +9,6 @@
import Foundation
protocol WebServiceAPI {
func fetchPeopleList(completion: [Person]? -> Void)
func fetchPerson(id: Int, completion: Person? -> Void)
func fetch(completion: [Person]? -> Void)
func fetch(id: Int, completion: Person? -> Void)
}
@@ -1,55 +0,0 @@
//
// StarWarsWebService.swift
// Dip
//
// Created by Olivier Halligon on 04/10/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
import Dip
class StarWarsWebService : WebServiceAPI {
let personFactory = Dependency.resolve() as PersonFactoryAPI
func fetchPeopleList(completion: [Person]? -> Void) {
let url = NSURL(string: "http://swapi.co/api/people/")!
fetchURL(url) { data in
guard let data = data else { completion(nil); return }
let persons: [Person]?
do {
persons = try self.personFactory.personListFromData(data)
} catch {
persons = nil
}
dispatch_async(dispatch_get_main_queue()) {
completion(persons)
}
}
}
func fetchPerson(id: Int, completion: Person? -> Void) {
let url = NSURL(string: "http://swapi.co/api/people/\(id)")!
fetchURL(url) { data in
guard let data = data else { completion(nil); return }
let person: Person?
do {
person = try self.personFactory.personFromData(data)
} catch {
person = nil
}
dispatch_async(dispatch_get_main_queue()) {
completion(person)
}
}
}
private func fetchURL(url: NSURL, completion: NSData? -> Void) {
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data: NSData?, resp: NSURLResponse?, error: NSError?) -> Void in
completion(data)
}
task.resume()
}
}
+14
View File
@@ -0,0 +1,14 @@
//
// Tags.swift
// Dip
//
// Created by Olivier Halligon on 05/10/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
enum PersonFormatterTag {
case MassHeight
case EyesHair
}
+8 -6
View File
@@ -12,7 +12,7 @@ import Dip
let kCellIdentifier = "Cell"
class ViewController: UIViewController {
let ws = Dependency.resolve() as WebServiceAPI
let ws = dip.resolve() as WebServiceAPI
var personList = [Person]()
@@ -23,7 +23,7 @@ class ViewController: UIViewController {
@IBAction func fetchPeople(sender: UIButton) {
sender.enabled = false
self.activityIndicator.startAnimating()
ws.fetchPeopleList { persons in
ws.fetch { persons in
self.activityIndicator.stopAnimating()
sender.enabled = true
self.personList = persons ?? []
@@ -46,17 +46,19 @@ extension ViewController : UITableViewDataSource {
let cell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier, forIndexPath: indexPath)
let person = personList[indexPath.row]
let formatter = Dependency.resolve(formatterTag) as PersonFormatterAPI
let formatter = dip.resolve(formatterTag) as PersonFormatterAPI
cell.textLabel?.text = formatter.textForPerson(person)
cell.detailTextLabel?.text = formatter.subtextForPerson(person)
return cell
}
var formatterTag: String {
var formatterTag: PersonFormatterTag {
switch displayModeSelector.selectedSegmentIndex {
case 0: return PersonFormatterTags.MassHeight.rawValue
default: return PersonFormatterTags.EyesHair.rawValue
case 0:
return .MassHeight
default:
return .EyesHair
}
}
}
+89
View File
@@ -0,0 +1,89 @@
import UIKit
import XCTest
import Dip
var dip = DependencyContainer<PersonFormatterTag>()
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)
}
}
}
-22
View File
@@ -1,22 +0,0 @@
import UIKit
import XCTest
import Dip
class Tests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
XCTAssert(true, "Pass")
}
}
+29 -29
View File
@@ -13,13 +13,12 @@ _Photo by [Matthew Hine](http://www.flickr.com/photos/75771631@N00), cc-by-2.0_
`Dip` is a simple **Dependency Injection Container**.
It's not true Dependency Injection, but it's damn close, and aimed to be as simple as possible.
It's inspired by `C#`'s [Unity Container](https://msdn.microsoft.com/library/ff647202.aspx).
It's inspired by `.NET`'s [Unity Container](https://msdn.microsoft.com/library/ff647202.aspx).
* You start by **registering all your dependencies, by associating a `protocol` to an `instance` or to an `instanceFactory`**. So for example you'll register an association between the `protocol WebServiceAPI` and an instance or type of `MyRealWebService`.
* Then anywhere in your application, you can call `Dependency.resolve()` to **resolve a `protocol` into an instance of a concrete type**.
* 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 (typically in your `AppDelegate` for your app, in your `setup` for your Unit Tests)
and **then only work with `protocols` in your code** (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 ([typically at the top of your `AppDelegate` for your app](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/AppDelegate.swift#L12-L21), 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/ViewController.swift#L15) (which only define an API contract), without worrying about the real implementation.
## Advantages of DI and loose coupling
@@ -41,61 +40,62 @@ pod "Dip"
### Register instances and instance factories
At the beginning of your app's life-cycle, you register instances and instance factories with protocols.
First, create a `DependencyContainer` and use it to register instances and factories with protocols, using those methods:
* `register(instance: _)` will register a singleton instance with a given protocol.
* `register(instanceFactory: _)` 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)`).
* if you give a `tag` in the parameter to `register()`, it will associate that instance or instance factory with this tag, which can be used later during `resolve` (see below).
* `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 can do that either:
* in `@objc class func initialize() {}` declared in an `extension Dependency`, so it's called automatically before the first call to `Dependency.resolve()`
* or using a `private let _dependencies: Void = { … }()` (for example at the top of your `AppDelegate.swift`) as a trick to have code called as soon as the app in loaded
* In your (non-hosted, standalone) unit tests, you'll probably declare them in your `func setup()` for example
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.
### Resolve dependencies
* `Dependency.resolve()` will return a new instance matching the requested protocol.
* `resolve()` will return a new instance matching the requested protocol.
* 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.
* `Dependency.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.
### 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)
### Example
Somewhere in your App target, register the dependencies. Best place to do that is probably in `Dependency.initialize()`:
Somewhere in your App target, register the dependencies:
```swift
extension Dependency {
@objc class func initialize() {
let env = ProductionEnvironment(analytics: true)
Dependency.register(instance: env as EnvironmentType)
Dependency.register(instance: WebService() as WebServiceType)
Dependency.register() { DummyFriendsProvider(user: $0 ?? "Jane Doe") as FriendsProviderType }
Dependency.register("me") { PlistFriendsProvider(plist: "myfriends") as FriendsProviderType }
}
let dip: DependencyContainer<String> = {
let dip = DependencyContainer<String>()
let env = ProductionEnvironment(analytics: true)
dip.register(instance: env as EnvironmentType)
dip.register(instance: WebService() as WebServiceType)
dip.register() { DummyFriendsProvider(user: $0 ?? "Jane Doe") as FriendsProviderType }
dip.register("me") { PlistFriendsProvider(plist: "myfriends") as FriendsProviderType }
return dip
}
```
> Do the same in your Unit Tests target & test cases, but obviously with different Dependencies registered, depending on what you want to test and what instances you need to inject to provide dummy implementations for your tests.
Then to use dependencies throughout your app, use `Dependency.resolve()`, like this:
Then to use dependencies throughout your app, use `dip.resolve()`, like this:
```swift
struct WebService {
let env: EnvironmentType = Dependency.resolve()
let env: EnvironmentType = dip.resolve()
func sendRequest(path: String, ) {
// ... use stuff like env.baseURL here
}
}
struct SomeViewModel {
let ws: WebServiceType = Dependency.resolve()
let ws: WebServiceType = dip.resolve()
var friendsProvider: FriendsProviderType
init(userName: String) {
friendsProvider = Dependency.resolve(userName)
friendsProvider = dip.resolve(userName)
}
func foo() {
ws.someMethodDeclaredOnWebServiceType()
+39 -36
View File
@@ -8,67 +8,75 @@
import Foundation
public class Dependency {
public typealias TagType = String
typealias InstanceType = Any
typealias InstanceFactory = TagType?->InstanceType
/**
* 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?
/**
* Internal representation of a key to associate protocols & tags to an instance factory
*/
private struct Key : Hashable, CustomDebugStringConvertible {
var protocolType: Any.Type
var associatedTag: TagType?
var hashValue: Int {
return "\(protocolType)-\(associatedTag)".hashValue
}
var debugDescription: String {
return "type: \(protocolType), tag: \(associatedTag)"
}
var hashValue: Int {
return "\(protocolType)-\(associatedTag)".hashValue
}
private static var dependencies = [Key: InstanceFactory]()
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
public class DependencyContainer<TagType : Equatable> {
typealias InstanceType = Any
typealias InstanceFactory = TagType?->InstanceType
private typealias Key = ProtoTagKey<TagType>
// MARK: - Reset all dependencies
private var dependencies = [Key : InstanceFactory]()
public static func reset() {
public init() {}
// MARK: Reset all dependencies
public func reset() {
dependencies.removeAll()
}
// MARK: - Register dependencies
// MARK: Register dependencies
/// Register a TagType?->T factory (which takes the tag as parameter)
public static func register<T : Any>(tag: TagType? = nil, instanceFactory: TagType?->T) {
public func register<T : Any>(tag: TagType? = nil, factory: TagType?->T) {
let key = Key(protocolType: T.self, associatedTag: tag)
dependencies[key] = { instanceFactory($0) }
dependencies[key] = { factory($0) }
}
/// Register a Void->T factory (which don't care about the tag used)
public static func register<T : Any>(tag: TagType? = nil, instanceFactory: Void->T) {
public func register<T : Any>(tag: TagType? = nil, factory: Void->T) {
let key = Key(protocolType: T.self, associatedTag: tag)
dependencies[key] = { _ in instanceFactory() }
dependencies[key] = { _ in factory() }
}
/// Register a Singleton instance
public static func register<T : Any>(tag: TagType? = nil, @autoclosure(escaping) instance instanceFactory: Void->T) {
public func register<T : Any>(tag: TagType? = nil, @autoclosure(escaping) instance factory: Void->T) {
let key = Key(protocolType: T.self, associatedTag: tag)
// FIXME: Make it thread-safe
dependencies[key] = { _ in
let instance = instanceFactory()
dependencies[key] = { _ in return instance }
let instance = factory()
self.dependencies[key] = { _ in return instance }
return instance
}
}
// MARK: - Resolve dependencies
// MARK: Resolve dependencies
/// Resolve a dependency
///
/// **Note** If a tag is given, it will try to resolve using the tag to generate a specific instance,
/// and fallback without the tag if not found with it
public static func resolve<T>(tag: TagType? = nil) -> T! {
public func resolve<T>(tag: TagType? = nil) -> T! {
let key = Key(protocolType: T.self, associatedTag: tag)
let nilKey = Key(protocolType: T.self, associatedTag: nil)
guard let factory = dependencies[key] ?? dependencies[nilKey] else {
@@ -78,8 +86,3 @@ public class Dependency {
}
}
// MARK: - Key equality
private func == (lhs: Dependency.Key, rhs: Dependency.Key) -> Bool {
return lhs.protocolType == rhs.protocolType && lhs.associatedTag == rhs.associatedTag
}