Created
February 25, 2016 21:23
-
-
Save potmo/4b502f132ce317557c87 to your computer and use it in GitHub Desktop.
Injection framework WIP for swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Cocoa | |
import Foundation | |
enum InjectionError: ErrorType { | |
case BindingNotFound(_:String) | |
} | |
protocol Initializable { | |
init() | |
} | |
protocol Binding{ | |
} | |
protocol InstanceBinding: Binding { | |
typealias InstanceType | |
var instanceType: InstanceType.Type {get} | |
var constructor: () throws -> InstanceType {get} | |
} | |
struct InstanceBindingContainer<T>: InstanceBinding { | |
typealias InstanceType = T | |
let instanceType: InstanceType.Type | |
let constructor: () throws -> InstanceType | |
} | |
class InstanceInjector { | |
private var bindings: [Binding] | |
init() { | |
bindings = [] | |
} | |
func bind<T>(toConstructor constructor: () throws -> T) -> Void { | |
bind(T.self, toConstructor: constructor) | |
} | |
func bind<T:Initializable, P>(interface: P.Type, toType type: T.Type) -> Void { | |
bind(P.self, toConstructor: type.init) | |
} | |
func bind<T, P>(interface: P.Type, toInstance instance: T) -> Void { | |
let constructor = {() throws -> T in | |
return instance | |
} | |
bind(P.self, toConstructor: constructor) | |
} | |
func bind<T, P, A0>(interface: P.Type, toConstructor constructor: (A0) throws -> T) -> Void { | |
let wrapperConstructor = { () throws -> T in | |
let argument0 = try self.resolve(A0.self) | |
let instance = try constructor(argument0) | |
return instance | |
} | |
bind(interface, toConstructor: wrapperConstructor) | |
} | |
func bind<T, P, A0, A1>(interface: P.Type, toConstructor constructor: (A0, A1) throws -> T) -> Void { | |
let wrapperConstructor = { () throws -> T in | |
let argument0 = try self.resolve(A0.self) | |
let argument1 = try self.resolve(A1.self) | |
let instance = try constructor(argument0, argument1) | |
return instance | |
} | |
bind(interface, toConstructor: wrapperConstructor) | |
} | |
func bind<T, P>(interface: P.Type, toConstructor constructor: () throws -> T) -> Void { | |
let protocolConstructor = { () throws -> P in | |
let instance = try constructor() | |
guard let protocolInstance = instance as? P else { | |
throw InjectionError.BindingNotFound("Type \(T.self) does not conform to type \(P.self)") | |
} | |
return protocolInstance | |
} | |
let binding = InstanceBindingContainer(instanceType: P.self, constructor: protocolConstructor) | |
bindings.append(binding) | |
} | |
func resolve<T>(type: T.Type) throws -> T { | |
return try resolve() | |
} | |
func resolve<T>() throws -> T { | |
for binding in bindings { | |
print("checking: \(binding)") | |
if let instanceBinding = binding as? InstanceBindingContainer<T> { | |
if instanceBinding.instanceType == T.self { | |
print("found: \(instanceBinding.instanceType)") | |
return try instanceBinding.constructor() | |
}else{ | |
print("found something that conforms but now what is bound: \(instanceBinding.instanceType)") | |
} | |
} | |
} | |
throw InjectionError.BindingNotFound("Could not resolve binding to \(T.self)") | |
} | |
} | |
struct EmptyStruct:TestProtocol, TestProtocol2, TestProtocol3, Initializable { | |
let thing = "I'm here" | |
init(){ | |
} | |
} | |
protocol TestProtocol {} | |
protocol TestProtocol2 {} | |
protocol TestProtocol3 {} | |
struct TestStruct: TestProtocol{} | |
struct TestStruct2: TestProtocol2{} | |
struct TestStruct3: TestProtocol3{} | |
struct TestStruct4 {} | |
protocol TestDependencyProtocol { | |
var dependency: TestProtocol {get} | |
} | |
protocol DoubleDependencyProtocol { | |
var dependency0: TestProtocol {get} | |
var dependency1: TestProtocol2 {get} | |
} | |
struct DependingStruct: TestDependencyProtocol { | |
let dependency: TestProtocol | |
} | |
struct DoubleDependencyStruct: DoubleDependencyProtocol { | |
let dependency0: TestProtocol | |
let dependency1: TestProtocol2 | |
} | |
protocol InitializableProtocol {} | |
struct InitializableStruct: Initializable, InitializableProtocol { | |
init(){} | |
} | |
let instanceInjector = InstanceInjector() | |
do { | |
instanceInjector.bind(toConstructor: {EmptyStruct()}) | |
instanceInjector.bind(TestProtocol.self, toConstructor: TestStruct.init) | |
instanceInjector.bind(TestProtocol2.self, toInstance: TestStruct2()) | |
instanceInjector.bind(TestProtocol3.self, toConstructor: {TestStruct3()}) | |
instanceInjector.bind(InitializableProtocol.self, toType: InitializableStruct.self) | |
instanceInjector.bind(TestDependencyProtocol.self, toConstructor: { dependency in return DependingStruct(dependency: dependency) }) | |
instanceInjector.bind(DoubleDependencyProtocol.self, toConstructor: {d0, d1 in return DoubleDependencyStruct(dependency0: d0, dependency1: d1)}) | |
instanceInjector.bind(TestStruct4.self, toConstructor: TestStruct4.init) | |
let e = try instanceInjector.resolve(TestProtocol.self) | |
let k = try instanceInjector.resolve(TestProtocol2.self) | |
let l = try instanceInjector.resolve(TestProtocol3.self) | |
let s = try instanceInjector.resolve(TestDependencyProtocol.self) | |
let f = try instanceInjector.resolve(DoubleDependencyProtocol.self) | |
let y = try instanceInjector.resolve(TestStruct4.self) | |
}catch InjectionError.BindingNotFound(let message){ | |
print("fail: \(message)") | |
} | |
// TODO: Find circlular dependencies | |
// TODO: Try to make T conform to P in a typed way | |
// TODO: Add .Singleton scope | |
// TODO: Let the injector be a builder so that it is not possible to bind after resolving has started | |
// TODO: It would be nice to have some kind of build chain that would be binder.bind(Protocol.self).to(Struct.self).in(.Singleton) | |
struct OngoingBinding<T>{ | |
let injector: Injector | |
let protocolType: T.Type | |
func to(constructor: () throws -> T) -> Void{ | |
injector.instanceInjector.bind(protocolType, toConstructor: constructor) | |
} | |
} | |
class Injector { | |
let instanceInjector: InstanceInjector | |
init(){ | |
instanceInjector = InstanceInjector() | |
} | |
func bind<T>(type: T.Type) -> OngoingBinding<T> { | |
return OngoingBinding<T>(injector: self, protocolType: T.self) | |
} | |
func resolve<T>(type: T.Type) throws -> T { | |
return try instanceInjector.resolve(type) | |
} | |
} | |
let i = Injector() | |
i.bind(TestProtocol.self).to(TestStruct.init) | |
i.bind(TestProtocol.self).to(TestStruct3.init) // fail | |
let t = try i.resolve(TestProtocol.self) | |
TODO: Write tests
TODO: maybe add a .bind(...).andVerify() to throw if it can't be resolved
TODO: print dependency tree
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
let resolver = injector.in(.Singleton).bind(TestProtocol.self).to(TestStuct.self)
.in(.Singleton).bind(TestProtocol.self).to(TestStuct.self)
.in(.Singleton).bind(TestProtocol.self).to(TestStuct.self).build()
try resolver.tryResolve(TestProtocol.self)
let stuff:? resolver.maybeResolve(TestProtocol.self)