iosswiftdependency-injectionfactoryobject-identity

How can I register 2 dependencies with the same abstract interface in my factory resolver?


I have a dependency container, that looks like this -

public protocol DependencyContainer {
  typealias FactoryClosure = (Self) -> AnyObject
  func register<Service>(type: Service.Type, closure: @escaping FactoryClosure)
  func resolve<Service>(type: Service.Type) -> Service
}
public final class CoreDependencyContainer: DependencyContainer {

  private var services: Dictionary<ObjectIdentifier, FactoryClosure> = [:]

  public func register<Service>(type: Service.Type, closure: @escaping FactoryClosure) {
    services[ObjectIdentifier(type)] = closure
  }

  public func resolve<Service>(type: Service.Type) -> Service {
    if let service = services[ObjectIdentifier(type)]?(self) as? Service {
      return service
    }
    preconditionFailure("Could not resolve service for \(type)")
  }

}

I have some services registered as follows:

dependencies.register(type: RemoteImageDataLoader.self, closure: { container in
  let httpClient = container.resolve(type: HTTPClient.self)
    return RemoteImageDataLoader(client: httpClient)
})

I want to however register 2 services that conform to the abstract type RemoteImageDataLoader.

dependencies.register(type: RemoteImageDataLoader.self, closure: { container in
  let httpClient = container.resolve(type: HTTPClient.self)
    return RemoteImageDataLoader(client: httpClient)
})

dependencies.register(type: RemoteImageDataLoader.self, closure: { container in
  let httpClient = container.resolve(type: AuthenticatedHTTPClient.self)
    return RemoteImageDataLoader(client: httpClient)
})

This obviously wont work as the second entry would replace the first in the services dictionary.

I tried to create a typealias to use instead, however this does not work.

dependencies.register(type: RemoteImageDataLoader.self, closure: { container in
  let httpClient = container.resolve(type: HTTPClient.self)
    return RemoteImageDataLoader(client: httpClient)
})

typealias AuthRemoteImageLoader = RemoteImageDataLoader

dependencies.register(type: AuthRemoteImageLoader.self, closure: { container in
  let httpClient = container.resolve(type: AuthenticatedHTTPClient.self)
    return RemoteImageDataLoader(client: httpClient)
})

How can I register the 2nd dependency in this case?

I'd like to avoid creating 2 versions of RemoteImageDataLoader as the only difference is the injected HTTPClient instance.


Solution

  • It doesn't work current as you are using ObjectIdentifier as a key in your dictionary, which the typealias does not change.

    You are basically registering the same object twice.

    Create an empty protocol that conforms to the same interface. You will also need to extend your to conform to your new, empty interface otherwise your resolve method will throw a preconditionFailure

    protocol AuthRemoteImageLoader: RemoteImageDataLoader { }
    extension RemoteImageDataLoader: AuthRemoteImageLoader { }
    

    You could also consider using generics to specify the HTTPClient interface being used, and extend RemoteImageDataLoader to act accordingly based on the type of client.