I am experimenting with the ServiceLocator pattern. I'd like to support lazy loading of items.
protocol Resolver {
func resolve<T> () -> T
}
protocol Register {
func register<T>(instance: T)
func register<T>(reference: @escaping () -> T)
}
final class LazyServiceLocator {
enum ObjectRegistry {
case instance(Any)
case reference(() -> Any)
func unwrap() -> Any {
switch self {
case let .instance(instance): return instance
case let .reference(reference): return reference()
}
}
}
private lazy var store: Dictionary<ObjectIdentifier, ObjectRegistry> = [:]
}
extension LazyServiceLocator: Resolver {
func resolve<T>() -> T {
let key = ObjectIdentifier(type(of: T.self))
if let item = store[key], let instance = item.unwrap() as? T {
switch item {
case .reference: register(instance: instance)
default: break
}
return instance
} else {
preconditionFailure("Could not resolve service for \(type(of: T.self))")
}
}
}
extension LazyServiceLocator: Register {
func register<T>(instance: T) {
let key = ObjectIdentifier(type(of: T.self))
store[key] = .instance(instance)
}
func register<T>(reference: @escaping () -> T) {
let key = ObjectIdentifier(type(of: T.self))
store[key] = .reference(reference)
}
}
This can run with the following in a playground
let foo = LazyServiceLocator()
class Test {
init() {
print("Test")
}
}
class OtherTest {
init(foo: Test) {
print("OtherTest")
}
}
foo.register(instance: Test())
foo.register(reference: {
return OtherTest(foo: foo.resolve())
})
let bar: Test = foo.resolve()
let boo: OtherTest = foo.resolve()
Commenting out let bar: Test = foo.resolve() still runs the print statement in it's init method, so I can see that works.
Commenting out let boo: OtherTest = foo.resolve() does not run the print statement, so I believe this also works.
I am very new to the pattern and Swift, so would appreciate some feedback on where this can be improved or what I have missed, being new to the language.