Dependency injection sounds like a very complex and terrifying topic. It sounds like the type of thing, that only Senior developers could ever do properly, and every Junior is doomed to sit quietly in conversations about it, quietly pretending to know what it is. The problem is only exacerbated in iOS development because the environment (read UIKit) actually discourages clean dependency injection principles (we’ll get to what this means and how to work around it).
In reality dependency injection is incredibly simple. In reality, its the type of thing that anyone who has been coding the “wrong” way for any amount of time could pick up if they read a short article with even halfway -well-thought-out examples. While it is a very simple topic, it will make your code far more maintainable and lays the foundations for testability. So without further ado…
A Short Article on DI with halfway-well-thought-out examples
Before we get into some half-cocked examples, let’s briefly talk about what dependency injection, or DI, is.
dependency injection is a technique whereby one object supplies the dependencies of another object.
Thank you, Wikipedia, for that vague and completely unhelpful definition. Let’s start somewhere simpler- like defining what dependency actually means.
A dependency is essentially any separate object that a class uses or relies on. Let’s look at an example of a simple notes app built WITHOUT DI. Then we’ll talk about how to fix it. Let’s assume we have two classes. The first handles processing images users want to save in notes. (I’m not concerned with the actual implementation of this class so I’ve left the function implementations empty).
The second class is our NoteController it handles creating Note instances within the app.
Now we run into our first roadblock. I need to resize the image that this function takes in, but the NoteController doesn’t know how to resize images; only the ImageController does. In other words, I need to access the functionality of the ImageController within a method of the NoteController. The easiest (and most common) way to get around this, is to make ImageController a shared instance. So I might do something like this. Remember, this is the “wrong” way without DI.
I can now very easily access an instance of the ImageController within the NoteController to resize the given image.
With this setup, our NoteController will always rely on the shared instance of the ImageController to resize its images. Some might even say the NoteController is “dependent” on the ImageController, or that the shared instance of the ImageController is a “dependency” of the NoteController. You’ll see examples of dependency anytime one of your classes (NoteController) needs functionality or resources from another object (ImageController).
If you’ve been in the development community beyond last Tuesday, you’ve likely heard someone probably named “Jeremy” go on a rant about how shared instances are the root of all evil. While I believe the righteous indignation is unwarranted, the code we just wrote is exactly the type of situation that would probably make “Jeremy” pop a blood vessel in his forehead. And while I hate “Jeremy”, he is right in this case. Because the code we just wrote is what’s referred to as “tightly coupled”. This is another fancy term, which just means our NoteController can never exist without the shared instance from ImageController. If something ever changes (and it always does), the NoteController doesn’t have the option to use anything else to resize images. It will always be stuck using that shared instance.
Let’s look at a better way. Instead of hiding NoteController’s dependency on ImageController, let’s draw attention to it. With proper DI, every class should declare its dependencies as properties. This means we’ll replace the static shared instance called within
with a variable at the class level.
While this might make “Jeremy” a bit happier, we’ve now pissed the compiler off.
We either need to give the ImageController property a default value or write an initializer for NoteController. We will need to solve this error with one of two approaches to DI.
Option 1: Constructor (Initializer) Injection
The idea behind constructor injection is to initialize an object with all of its dependencies. That means we simply need to write an initializer for our NoteController which takes in an instance of ImageController.
Whatever object uses our NoteController will need to initialize it with an instance of ImageController. It’s that simple. Constructor based injection is the ideal DI approach for two reasons. First, constructor injection allows for immutable dependencies, i.e, we can keep imageController as a let, meaning I won’t have to worry about the instance changing somewhere else in the code. Second, I know that every instance of `NoteController` will have a non-nil value for its imageController property. I can freely use this instance without guard statements or worrying about “unhappy paths” the code might take.
Option 2: Setter Injection
Setter Injection means we are going to make the imageController property optional, and “set” it later. With setter injection, our NoteController might look something like this.
Notice that we had to use optional chaining to call
which causes the resulting resizedImage to be optional, which forces us to guard against the resizeImage being nil and return nil if it is, which forces us to change the return type of the function to Note?. Optionals tend to breed more optionals; introducing one optional tends to carry throughout the class which muddies up the code all over the place. Also, notice setter injection forced us to make our imageController property an Optional and a var which means our code becomes harder to read and logic through. If I’m looking at NoteController how am I supposed to know when and if the Optional imageController property will be set?
Setter Injection is incredibly popular in iOS development, especially when dealing with any dependency on a `UIViewController` or anything involving a delegate. While, the approach is more flexible than the alternative, debugging setter injection and nil dependencies lead to less concise code. I’d recommend a healthy bias toward constructor injection when possible. The key to both of these approaches, however, is that dependencies are determined outside of the class which owns them (in this instance NoteController).
Why should you care?
On its own, Dependency Injection seems like semantics, but the principle of DI is the foundation for loosely coupled and testable code. In short, injecting an instance of any dependency means you can replace it with another instance when you need to.