Skip to content

Instantly share code, notes, and snippets.

@stefc
Last active March 17, 2016 21:29
Show Gist options
  • Save stefc/d6b09a39c9e5bd7c9e9e to your computer and use it in GitHub Desktop.
Save stefc/d6b09a39c9e5bd7c9e9e to your computer and use it in GitHub Desktop.
ligthweight IoC Container & ServiceLocator in Swift
//
// 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)
}
}
@stefc
Copy link
Author

stefc commented Mar 17, 2016

Added parent-child hierachy
Added same type can be registered under different name identifier

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