Skip to content

Named definitions

Ilya Puchka edited this page Dec 10, 2016 · 4 revisions

Sometimes you need to register different factories or different implementations of the same protocol. DependencyContainer does not capture the concrete type that you instantiate or any implementation details of registered factory, so if you try this the latest definition will override a previous one:

container.register() { URLSessionNetworkLayer(baseURL: "http://prod.myapi.com/api/") as NetworkLayer }
container.register() { URLSessionNetworkLayer(baseURL: "http://dev.myapi.com/api/") as NetworkLayer }

let network = try! container.resolve() as NetworkLayer
network.baseURL // http://dev.myapi.com/api/

To solve that you can use named definitions by providing tag parameter to register method.

  • If you provide a tag value in the parameter to register, it will associate created definition with this tag;
  • When you provide a tag value to resolve, container will try to find a matching definition associated with this tag. If it doesn't find any, it will fallback to a matching definition not associated with any tag (see note)
  • Tags can be of any type that conforms to DependencyTagConvertible protocol. String and Int conform to this protocol, RawRepresentable type with String or Int raw value types provides default implementation of DependencyTagConvertible.

Think about untagged definitions as a default factory where you can register more specialized named factories.

enum Environment: String, DependencyTagConvertible {
    case Production
    case Development
}

container.register(tag: Environment.Production) { URLSessionNetworkLayer(baseURL: "http://prod.myapi.com/api/") as NetworkLayer }
container.register(tag: Environment.Development) { URLSessionNetworkLayer(baseURL: "http://dev.myapi.com/api/") as NetworkLayer }

let networkLayer = try! container.resolve(tag: Environment.Production) as NetworkLayer
network.baseURL // http://prod.myapi.com/api/

If all definitions of the component are registered with a tag then you will not be able to resolve this component without providing a tag.

Note on fallback

Even when falling back to untagged definition resolved instance will be associated with the tag that was passed to resolve. This way even though the same definition was used container will provide separate instances. They can be later reused only if you resolve using the same tag. That affects all scopes including singleton scopes.

container.register(.singleton) { ServiceImp() as Service }
//resolved instance will be associated with empty tag
let service = container.resolve() as Service

//resolved instance will be associated with tag "one"
let service1 = container.resolve(tag: "one") as Service
//will not reuse service as it was resolve for different tag
service !== service1

//resolved instance will be associated with tag "another"
let service2 = container.resolve(tag: "another") as Service
//will not reuse service1, nor service as they were resolved for different tag
service2 !== service1
service2 !== service

let service1_2 = container.resolve(tag: "one") as Service
//will reuse service1 as it was resolved for the same tag
service1 === service1_2

See also: Resolving multiple instances