Dependency Injection is a really interesting and useful pattern, but it's often misunderstood. In this chapter, we tried to present every aspect of Dependency Injection, debunking its myths and falsehoods, and showing when and how to use it.
We started the exploration of Dependency Injection with its definition, from Martin Fowler's seminal article, and from several other influential sources. A pattern is useless without the context in which it must be applied. We identified two major scenarios where DI is useful: Separation of concerns, to help the development of loosely-coupled components and hence create more flexible and maintainable code, and Testing, where problematic collaborators could be replaced by Test Double, a simplified and controlled version of the dependencies. There are different ways of implementing DI, such as injecting in the constructor, which is the one to use when possible, injecting using properties, to be used with frameworks such as UIKit, method Injection, and Ambient Context.
At this point in our journey, we realized that finding a place where we can bind the dependencies is a crucial aspect of the pattern; using the Composition Root technique will help us to do it in a clean and effective way. When applying a pattern, we need to take particular care not to slip into applying it in the wrong way. We then explored the most common DI anti-patterns, starting with Control Freak, where it looks like we using DI, but actually the code it is strongly coupled, to Bastard Injection, where a shortcut we take to make the creation of object easier then creates a dependency between modules, and finally to the Service Locator, the most insidious one, which could lead to creating relationships with the modules that nullify the advantages of DI.
Finally, we explored two DI Containers to use with Swift: Typhoon, the most common in the iOS ecosystem, but that still uses the Objective-C runtime libraries, and the more Swift-idiomatic Swinject.
In the next chapter, we'll continue our exploration of Swift patterns by moving into sorting asynchronous code, using other languages' common patterns, such as Futures and Promises, and a more sophisticated solution such as Reactive Programming.