Last active
March 17, 2016 21:29
-
-
Save stefc/d6b09a39c9e5bd7c9e9e to your computer and use it in GitHub Desktop.
ligthweight IoC Container & ServiceLocator in 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
// | |
// ServiceLocator.swift | |
// | |
// Created by Stefan Böther on 12.03.16. | |
// | |
// Inspired by the following => | |
// https://github.com/DivineDominion/mac-appdev-code/blob/master/DDDViewDataExample/ServiceLocator.swift | |
// https://gist.github.com/werediver/66ff8f13c900e9871070 | |
// https://github.com/Swinject/Swinject | |
import Foundation | |
/* | |
leightweight IoC - Container & ServiceLocator in Swift | |
usage example : | |
create a new container | |
let container = createContainer() | |
get the service locator from a container | |
let locator = container.asServiceLocator() | |
register an instance in the container | |
container.registerInstance( String("abc") ) | |
register a lazy type in the container | |
container.registerType { String("lazy") } | |
resolve a type from a container | |
let abc = try container.resolve() as String | |
get an instance from a service locator | |
let abc = try locator.getInstance() as String | |
*/ | |
// lightweight IoC | |
public protocol Container { | |
func resolve<T>(name: String?) throws -> T | |
func registerType<T>(recipe: () -> T) -> Container | |
func registerInstance<T>(instance: T) -> Container | |
func registerType<T>(name: String?, recipe: () -> T) -> Container | |
func registerInstance<T>(name: String?, instance: T) -> Container | |
func createChildContainer() -> Container | |
} | |
// service locator pattern | |
public protocol ServiceLocator { | |
func getInstance<T>(name: String?) throws -> T | |
func getInstance<T>() throws -> T | |
} | |
public enum LocatorError: ErrorType { | |
case InvalidType(typeName: String) | |
} | |
public func createContainer() -> Container { | |
return ConcreteContainer() | |
} | |
extension Container { | |
func asServiceLocator() -> ServiceLocator { | |
return ContainerServiceLocator(container: self) | |
} | |
} | |
typealias ServiceLocatorProvider = () -> ServiceLocator | |
final public class Locator { | |
private static var provider : ServiceLocatorProvider? | |
class var Current : ServiceLocator { | |
get { | |
return provider!() | |
} | |
} | |
class func SetLocatorProvider(newProvider : ServiceLocatorProvider) { | |
provider = newProvider | |
} | |
} | |
internal typealias FunctionType = Any | |
internal struct RegistryKey { | |
private let factoryType: FunctionType.Type | |
private let name: String? | |
internal init(factoryType: FunctionType.Type, name: String? = nil) { | |
self.factoryType = factoryType | |
self.name = name | |
} | |
} | |
// MARK: Hashable | |
extension RegistryKey: Hashable { | |
var hashValue: Int { | |
return String(factoryType).hashValue ^ (name?.hashValue ?? 0) | |
} | |
} | |
// MARK: Equatable | |
func == (lhs: RegistryKey, rhs: RegistryKey) -> Bool { | |
return lhs.factoryType == rhs.factoryType && lhs.name == rhs.name | |
} | |
private class ConcreteContainer : Container { | |
let parentContainer : Container? | |
/// Registry record | |
enum RegistryRec { | |
case Instance(Any) | |
case Recipe(() -> Any) | |
func unwrap() -> Any { | |
switch self { | |
case .Instance(let instance): | |
return instance | |
case .Recipe(let recipe): | |
return recipe() | |
} | |
} | |
} | |
private init(_ parentContainer: Container? = nil) { | |
self.parentContainer = parentContainer | |
registerInstance(self as Container) | |
} | |
/// Service registry | |
private lazy var reg: Dictionary<RegistryKey, RegistryRec> = [:] | |
private func typeName(some: Any) -> String { | |
return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)" | |
} | |
private func registerType<T>(recipe: () -> T) -> Container { | |
return self.registerType(nil, recipe: recipe) | |
} | |
private func registerInstance<T>(instance: T) -> Container { | |
return self.registerInstance(nil, instance: instance) | |
} | |
private func registerType<T>(name: String?, recipe: () -> T) -> Container { | |
reg[RegistryKey(factoryType: T.self, name: name)] = .Recipe(recipe) | |
return self | |
} | |
private func registerInstance<T>(name: String?, instance: T) -> Container { | |
reg[RegistryKey(factoryType: T.self, name: name)] = .Instance(instance) | |
return self | |
} | |
private func resolve<T>(name : String? = nil) throws -> T { | |
let key = RegistryKey(factoryType: T.self, name: name) | |
if let registryRec = reg[key] { | |
let instance = registryRec.unwrap() as! T | |
// Replace the recipe with the produced instance if this is the case | |
switch registryRec { | |
case .Recipe: | |
registerInstance(instance) | |
default: | |
break | |
} | |
return instance | |
} | |
if let next = parentContainer { | |
return try next.resolve(name) | |
} | |
else { | |
throw LocatorError.InvalidType(typeName: String(T.self)) | |
} | |
} | |
private func createChildContainer() -> Container { | |
return ConcreteContainer(self) | |
} | |
} | |
private class ContainerServiceLocator : ServiceLocator { | |
let container : Container | |
init(container: Container) { | |
self.container = container | |
} | |
func getInstance<T>(name: String?) throws -> T { | |
return try container.resolve(name) | |
} | |
func getInstance<T>() throws -> T { | |
return try container.resolve(nil) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Added parent-child hierachy
Added same type can be registered under different name identifier