Skip to content

Instantly share code, notes, and snippets.

@potmo
Created February 25, 2016 21:23
Show Gist options
  • Save potmo/4b502f132ce317557c87 to your computer and use it in GitHub Desktop.
Save potmo/4b502f132ce317557c87 to your computer and use it in GitHub Desktop.
Injection framework WIP for swift
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)
@potmo
Copy link
Author

potmo commented Feb 26, 2016

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)

@potmo
Copy link
Author

potmo commented Feb 26, 2016

TODO: Write tests

@potmo
Copy link
Author

potmo commented Feb 26, 2016

TODO: maybe add a .bind(...).andVerify() to throw if it can't be resolved

@potmo
Copy link
Author

potmo commented Feb 26, 2016

TODO: print dependency tree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment