Design Patterns: Dependency Injection

Even though dependency injection is a topic that is rarely taught to beginners, it is a design pattern that deserves more attention. Many developers avoid dependency injection, because they don't know what it means or because they think that they don't need it.

In this article, I'm going to try to convince you of the value of dependency injection. To do this, I will introduce you to dependency injection by showing you how simple it is in its simplest form.

1. What Is Dependency Injection?

Much has been written about dependency injection and there are a bunch of tools and libraries that aim to simplify dependency injection. There is one quote, however, that captures the confusion many people have around dependency injection.

"Dependency Injection" is a 25-dollar term for a 5-cent concept. - James Shore

Once you grasp the idea that underlies dependency injection, you will also understand the above quote. Let's start with an example to illustrate the concept.

An iOS application has many dependencies and your application may rely on dependencies you're not even aware of, that is, you don't consider them dependencies. The following code snippet shows the implementation of a UIViewController subclass named ViewController. The implementation includes a method named saveList:. Can you spot the dependency?

The most commonly overlooked dependencies are the ones that we rely on the most. In the saveList: method, we store an array in the user defaults database, accessible through the NSUserDefaults class. We access the shared defaults object by invoking the standardUserDefaults method. If you're somewhat familiar with iOS or OS X development, then you're likely to be familiar with the NSUserDefaults class.

Storing data in the user defaults database is fast, easy, and reliable. Thanks to the standardUserDefaults method, we have access to the user defaults database from anywhere in the project. The method returns a singleton that we can use whenever and wherever we want. Life can be beautiful.

Singleton? Whenever and wherever? Do you smell something? Not only do I smell a dependency, I also smell a bad practice. In this article, I don't want to open up a can of worms by discussing the use and misuse of singletons, but it is important to understand that singletons should be used sparingly.

Most of us have become so used to the user defaults database that we don't see it as a dependency. But it certainly is one. The same is true for the notification center, which we commonly access through the singleton accessible through the defaultCenter method. Take a look at the following example for clarification.

The above scenario is very common. We add the view controller as an observer for notifications with name UIApplicationWillEnterForegroundNotification and remove it as an observer in the dealloc method of the class. This adds another dependency to the ViewController class, a dependency that is often overlooked—or ignored.

The question you may be asking yourself is "What is the problem?" or better "Is there a problem?" Let's start with the first question.

What Is the Problem?

Based on the above examples, it may seem as if there is no problem. That isn't entirely true though. The view controller depends on the shared defaults object and the default notification center to do its work.

Is that a problem? Almost every object relies on other objects to do its work. The issue is that the dependencies are implicit. A developer new to the project doesn't know the view controller relies on these dependencies by inspecting the class interface.

Testing the ViewController class will also prove to be tricky since we don't control the NSUserDefaults and NSNotificationCenter classes. Let's look at some solutions to this problem. In other words, let's see how dependency injection can help us solve this problem.

2. Injecting Dependencies

As I mentioned in the introduction, dependency injection is a very simple concept. James Shore has written a great article about the simplicity of dependency injection. There is one other quote from James Shore about what dependency injection is at its very core.

Dependency injection means giving an object its instance variables. Really. That's it. - James Shore

There are a number of ways to accomplish this, but it's important to first understand what the above quote means. Let's see how we can apply this to the ViewController class.

Instead of accessing the default notification center in the init method through the defaultCenter class method, we create a property for the notification center in the ViewController class. This is what the updated interface of the ViewController class looks like after this addition.

This also means that we need to do a bit of extra work when we initialize an instance of the ViewController class. As James writes, we hand the ViewController instance its instance variables. That is how simple dependency injection is. It's a fancy name for a straightforward concept.

As a result of this change, the implementation of the ViewController class changes. This is what the init and dealloc methods look like when injecting the default notification center.

Note that we don't use self in the dealloc method. This is considered a bad practice since it may lead to unexpected results.

There is one problem. Can you spot it? In the initializer of the ViewController class, we access the notificationCenter property to add the view controller as an observer. During initialization, however, the notificationCenter property hasn't been set yet. We can solve this problem by passing the dependency as a parameter of the initializer. This is what that looks like.

To make this work, we also need to update the interface of the ViewController class. We omit the notificationCenter property and add a method declaration for the initializer we created.

In the implementation file, we create a class extension in which we declare the notificationCenter property. By doing so, the notificationCenter property is private to the ViewController class and can only be set by invoking the new initializer. This is another best practice to keep in mind, only expose properties that need to be public.

To instantiate an instance of the ViewController class, we rely on the initializer we created earlier.

3. Benefits

What have we accomplished by explicitly injecting the notification center object as a dependency?

Clarity

The interface of the ViewController class unequivocally shows that the class relies or depends on the NSNotificationCenter class. If you're new to iOS or OS X development, this may seem like a small win for the complexity we added. However, as your projects become more complex, you will learn to appreciate every bit of clarity you can add to a project. Explicitly declaring dependencies will help you with this.

Modularity

When you start using dependency injection, your code will become much more modular. Even though we injected a specific class into the ViewController class, it is possible to inject an object that conforms to a specific protocol. If you adopt this approach, it will become much easier to replace one implementation with another.

In the above interface of the ViewController class, we declare another dependency. The dependency is an object that conforms to the MyProtocol protocol. This is where the true power of dependency injection becomes apparent. The ViewController class doesn't care about the type of someObject, it only asks that it adopts the MyProtocol protocol. This makes for highly modular, flexible, and testable code.

Testing

Even though testing isn't as widespread among iOS and OS X developers as it is in other communities, testing is an key topic that gains in importance and popularity. By adopting dependency injection, you will make your code much easier to test. How would you test the following initializer? That's going to be tricky. Right?

The second initializer, however, makes this task much easier. Take a look at the initializer and the test that goes with it.

The above test makes use of the OCMock library, an excellent mocking library for Objective-C. Instead of passing in an instance of the NSNotificationCenter class, we pass in a mock object and verify whether the methods that need to be invoked in the initializer are indeed invoked.

There are several approaches to test notification handling and this is—by far—the easiest I've come across. It adds a bit of overhead by injecting the notification center object as a dependency, but the benefit outweighs the added complexity in my opinion.

4. Third Party Solutions

I hope I've convinced you that dependency injection is a simple concept with a simple solution. There are, however, a number of popular frameworks and libraries that aim to make dependency injection more powerful and easier to manage for complex projects. The two most popular libraries are Typhoon and Objection.

If you're new to dependency injection, then I strongly recommend to start using the techniques outlined in this tutorial. You first need to properly understand the concept before relying on a third party solution, such as Typhoon or Objection.

Conclusion

The goal of this article was to make dependency injection easier to understand for people who are new to programming and unfamiliar with the concept. I hope I have convinced you of the value of dependency injection and the simplicity of the underlying idea.

There are a number of excellent resources about dependency injection. James Shore's article about dependency injection is a must-read for every developer. Graham Lee also wrote a great article aimed at iOS and OS X developers.

Tags:

Comments

Related Articles