In this article I’m going to explain how you can use Dependency Injection to simplify your iOS ViewControllers to create smaller, more extensible ViewControllers and re-usable Services classes. I will also demonstrate that you can use your Storyboard as a way to configure the dependencies for your ViewController instances.
Dependency Injection is pattern that allows your classes to depend on small re-usable service objects without needing to know how the service objects are created.
This explanation might be a little confusing, so I’ll port one of Martin Fowler’s examples from the link above into Objective-C to clarify.
Take the following class:
As you can see,
MovieListViewController has a dependency on an instance of
ColonDelimitedMovieFinder which it uses to provide a list of movies when the view is loaded. It could be worse: the movie finding could be a method in the controller (violating SRP1), but we can do even better.
In the following example we make finding movies into a small interface2 upon which the
MovieListViewController will depend via a property:
As you can see, we introduced a property for
movieFinder to replace the member variable. Also, we introduced a new protocol,
MovieFinder which is used as the type of the new property. This is a very simple case of Property Injection3.
MovieListViewController is not responsible for creating its movie finding class, instead it is now provided via a publicly writable property.
You may notice that any reference to
ColonDelimitedMovieFinder was removed. This doesn’t mean it is no longer a class, but instead it now adheres to the new small interface, for example:
In addition to injecting this dependency, we have also now made
MovieListViewController extensible: it can take any instance of any class that adheres to this new protocol. Now, in addition to
ColonDelimitedMovieFinder, there could be a
IMDBWebServiceMovieFinder on option as well.
Putting Dependencies in the Storyboard
The previous code example is an improvement on where it started, but there is still the open question of where in the codebase the
movieFinder property on
MovieListViewController gets instantiated and assigned.
There are many ways to do this including:
- assign a constructed instance of
MovieFinderin any previous ViewController’s
MovieListViewControlleruse ServiceLocator to locate an instance4
- specify an instance in the Storyboard
Let’s look at how we can use the Storyboard to select the concrete instance of
MovieFinder for any given
Assume the following definitions exist somewhere in the project:
First, add an Object to your ViewController’s scene in the storyboard. Here I have named it
IMDBWebServiceFinder with a class type of
Then control-drag from the
MovieList ViewController to the
And select the outlet named
You can see the outlet connected in the following screenshot:
Now, when the
MovieList scene is loaded from the Storyboard, its
MovieListViewController will be populated with an instance of
IMDBWebServiceMovieFinder. This configuration is stored in the Storyboard and can be changed entirely using only interface builder. Another way to say that is: we are using the Storyboard as our Dependency Injection configuration.5
At this point you might be asking why you would ever want to do this. Why not just create your instance of
viewDidLoad: and call it a day?
This is certainly a fine idea and worth considering depending on your circumstances. One of the benefits of this dependency injection method is it allows for the same ViewController implementation to be used for different storyboard scenes with different dependencies.
For example, perhaps this application has a tab for finding movies from IMDB and a tab for finding movies from Netflix.
All you need is a new class adhering to
Now you can create a ViewController in the Storyboard, set the class to
MovieListViewController, and instead of connecting a Storyboard Object with type
IMDBWebServiceMovieFinder, connect one of type
Now, in your
MovieListViewController, movies can be fetched, iterated, and displayed using the same code, yet the finding of movies is now allowed to vary by this dependency injection.
MovieListViewController in this example is “open for extension, but closed for modification.”6 Or put another way, some of its behaviors (finding movies) can change without the code for listing them needing to change.
One of the main open issues with this approach that it does not yet address dependencies with dependencies. I hope to address this in a future article as it is definitely a larger topic.
Another issue deals with segue names. If you are attempting to use the same ViewController in multiple places in your storyboard, then doing the following is dangerous:
This is because the segue name is essentially configuration, and that configuration is not being injected from the Storyboard7. One way to put your segue names into the Storyboard is to create a Runtime Attribute in the storyboard containing the the name of the segue which is then available to your ViewController at runtime.
- Dependency Injection is an effective and simple way to reduce the size of your iOS ViewControllers.
- It also allows your view controllers to be open for behavior changes without requiring modifications to the ViewControllers’ implementation.
- You can optionally inject your dependencies via Objects in your Storyboard
- This allows for re-use of code, and for treating your Storyboard as configuration.
- There are potential issues with this solution, namely dependencies with dependencies and putting segue names in your code.
If you are familiar with storyboard objects, then I hope this was helpful in showing how you can use them for dependency injection.
If you are new to dependency injection, then I hope this was a helpful intro showing just one of the many ways you can use it to improve your code and make your programs easier to write and maintain!
Main photo by Flickr user MJM Photographie
You could, for example, lazy-load some
movieFinderfrom the Service Locator in
movieFinder’s getter. This is great for testing as you can still assign a stub or mock of this and bypass the lazy load. ↩