1
\$\begingroup\$

I am experimenting with theServiceLocator 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 outlet bar: Test = foo.resolve() still runs the print statement in it's init method, so I can see that works.

Commenting outlet 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.

tieskedh's user avatar
tieskedh
8425 silver badges14 bronze badges
askedMay 14, 2020 at 8:52
Teddy K's user avatar
\$\endgroup\$

1 Answer1

1
\$\begingroup\$

This is mostly good, but it can be improved.

You might want to constrain the Resolver and Register protocols to classes. Since you want to share instances this won’t work with structs.

protocol Register: class { ... }

Yourstore property doesn’t have to be lazy. Initializing an empty dictionary is cheap, so you can do that directly when your LazyServiceLocator is initialized.

The biggest issue I found is the ObjectIdentifier you use as the dictionary key. You don’t store the type of T (which would beT.self) but the type of the type of T - it’s metatype. I’m not quite sure if this can lead to problems, but it is unnecessary. Just useObjectIdentifier(T.self).

The other thing you might want to consider is renaming yourregister(reference:) method toregister(factory:). Calling something that creates an object reference is rather uncommon.

And finally you might want to consider adding an overload forresolve that takes the requested type as a parameter:

extension Resolver {    func resolve<T>(_ type: T.Type) -> T {        return resolve()    }}

With that you can writelet x = resolve(Foo.self) instead oflet x: Foo = resolve(). IMHO this is a bit more readable.

answeredJun 12, 2020 at 16:56
\$\endgroup\$
0

You mustlog in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.