Skip to content

Tips & Tricks

Ilya Puchka edited this page Apr 27, 2016 · 17 revisions

####Runtime arguments and auto-wiring

When using auto-wiring we effectively create definition with a factory that accepts runtime arguments, so that there are two ways to resolve the component - with auto-wiring just calling resolve() or by calling resolve(withArguments:...) and providing those runtime arguments. To make an intention clear specify names and types of dependencies that are supposed to be passed as arguments to resolve(withArguments:) and not resolved by auto-wiring.

//use such registration style when you plan to use auto-wiring to resolve `APIClient`
container.register { APIClientImp(credentialsStorage: $0) as APIClient }

//use such registration style when you plan to pass dependency as runtime argument
container.register { (url: NSURL) in APIClientImp(url: url) as APIClient }

When you will read your configuration after some time the intention will be clear for you. Also you will directly see what are the types of runtime arguments.

####Registering existing instances

It is possible to register existing instances in the container:

let keychain = ...
container.register { keychain as KeychainService }

Note that scope will be ignored in this case and this component will be always resolved to the registered instance, making it equivalent to .Singleton scope.

####Resolving multiple instances

With named definitions you can register alternative implementations for the same protocol. Here is how you can resolve all of them as array:

enum Services: String, DependencyTagConvertible {
  case GMail, Yahoo, Outlook
  let allValues: [Services] = [.GMail, .Yahoo, .Outlook]
}
container.register(tag: Services.GMail) { GmailService() as ThirdPartyEmailService }
container.register(tag: Services.Yahoo) { YahooService() as ThirdPartyEmailService }
container.register(tag: Services.Outlook) { OutlookService() as ThirdPartyEmailService }

let allServices: [ThirdPartyEmailService] = try Services.allValues.map({ try container.resolve(tag: $0) })

####Registering values

You can easily register value types or any "primitive" values in a container:

container.register(tag: "api") { NSURL(string: ...)! }
container.register { try APIClientImp(url: container.resolve(tag: "api") as NSURL) as APIClient }

let container = try! container.resolve(tag: "api") as APIClient

This will perfectly fit auto-wiring:

container.register(tag: "api") { NSURL(string: ...)! }
container.register { try APIClientImp(url: $0) as APIClient }

let container = try! container.resolve(tag: "api") as APIClient

Note: here api client does not really depend on runtime value, it depends on "configuration" value which is not intended to be changed during application runtime. Do not use this technique if your component depends on dynamic runtime values.

####Service locator anti-pattern

When using any DI container it is very easy to end up with Service Locator anti-pattern. Service locator is some service that you query for dependencies instead of creating them manually. It may seem that it is the same as DI container. And it is really can be implemented as DI container. The difference that makes it an anti-pattern is how you use it. When you access DI container directly from you classes instead of passing dependencies to it through constructor, property or method injection - you are making it a service locator. Instead you should access container directly only inside composition root. So this tip is do not use DI container as Service locator.

####Implementing composition root TODO

####Constructor over-injection TODO