Dependency Injection, why do you need it? You start your new app with good intentions. You strive to keep the code clean but quickly things go wrong. You hook your app to a set of services, add analytics, add some logging. Quickly you find there are dependencies all over your app. It is almost impossible to run automated tests against your app. Difficult to reproduce strange behaviour users are reporting out in the wild. It might be time for Dependency Injection.
Depend on abstractions. Do not depend on concrete classes
Dependencies also cause no end of problems as you try and switch out features within your app. Moving from Flurry to Mixpanel isn’t very fun. It is likely Flurry has been integrated into every ViewController within your codebase. When the dependency has been injected into the codebase however things are a great deal easier. This technique is referred to as the Dependency Inversion Principle. Head First Design Patterns by Eric Freeman has some guidelines for apply the principle effectively:
No variable should hold a reference to a concrete class.
If you use new, you’ll be holding a reference to a concrete class.
No class should derive from a concrete class.
If you derive from a concrete class, you’re depending on a concrete class. Derive from an abstraction, like an interface or an abstract class.
No method should override an implemented method of any its base classes.
If you override an implemented method, then your base class wasn’t really an abstraction to start with. Those methods implemented in the base class are meant to be shared by all your subclasses.
These guidelines should be treated as such. It is impossible to follow all of them, you should have a good thought out reason for breaking them though.
Typhoon. Dependency Injection For Objective-C & Swift
This blog post looks at adding Dependency Injection to an Objective-C app using Typhoon. There are many Dependency Injection Frameworks available for Objective-C. I like the approach taken by the Typhoon Framework and found it easy to use on a project I am currently working on.
So lets get started. Start up Xcode 6 and go to File > New > Project and choose the Single View Application template and click Next
To include Typhoon into the project we are going to use Cocoapods. If you haven’t got Cocoapods installed already you can find instructions on their site. Once you have Cocoapods installed open a Terminal window in the directory you saved DependencyInjectionSample to and run:
Open up the Podfile that was just created and add the following text, then save and close the file.
target :DependencyInjectionSample do
pod 'Typhoon', :head
Back in the Terminal window run pod install
I want to keep this post focussed on Dependency Injection so I have used some code from the excellent book iOS Programming: The Big Nerd Ranch Guide. recommended for any iOS Developer. The diagram below shows the structure of the application we are going to build in part. The diagram comes from the book referenced earlier.
Typically we would reference the BNRItemStore object directly from the BNRItemsViewController. This is not what we want though. We want to inject the BNRItemStore instance into the BNRItemsViewController. Following the principle “no variable should hold a reference to a concrete class” we will create a protocol to reference the BNRItemStore object.
Creating the Store Object
iOS Programming: The Big Nerd Ranch Guide describes the Model-View-Controller-Store design pattern. Within this pattern the Controller asks the store for the model object it needs. Unlike the traditional Model-View-Controller design pattern, the controller doesn’t need to know how model objects are populated.
We are going to start out by creating a store object. Within the Project Navigator (⌘ + 1) within the DependencyInjectionSample group right-click on the DependencyInjectionSample folder and select New Group and give the group a name of Store.
Right-click on the Store group and select New File. Select Objective-C File and click Next.
Enter the following details in the next screen and once done, click Next and Create the file.
The BNRItemStore.h is going to be our protocol class. Open up the BNRItemStore.h file and edit as follows:
We are now going to set-up the user interface. Open Main.storyboard and delete the View Controller which is currently there. We are going to add our own View Controller now. Within the Object Library drag a Table View Controller onto the canvas.
Within the Identity Inspector (⌘ + ⌥ + 3) set the Class to BNRItemsViewController. Within the Attributes Inspector (⌘ + ⌥ + 4) tick the Is Initial View Controller option.
If you were to run this application now the application compiles but you just see a blank screen.
Adding dependency injection
You see a blank screen because BNRItemsViewController has a property itemStore which needs to have a valid BNRItemStore instance. We are going to use dependency injection to make this happen.
Within the Project Navigator (⌘ + 1) within the DependencyInjectionSample group right-click on the DependencyInjectionSample folder and select New Group and give the group a name of Assembly.
Right-click on the Assembly group and select New File. Select Cocoa Touch Class and click Next. Enter the following details in the next screen and once done, click Next and Create the file.
A Quick Start to dependency injection using the Typhoon Framework is available here.
Key concept: At build-time an assembly returns definitions, however at run-time we’ll use the same interface to return Typhoon-built components.
We have set-up definitions for the types we want to return and we want Typhoon to return the appropriately classes when the application runs. To do this we are going to bootstrap Typhoon into the App-Info.plist. Open up the Project Navigator (⌘ + 1), in the Supporting Files Group you will see an Info.plist file. Open the Info.plist file and hover over the last entry in the list and you will see a + icon appear. Click the + icon and a new row will appear. Change the title of the row to TyphoonInitialAssemblies change the type to Array. Click the + icon again to add a new Array item to the TyphoonInitialAssemblies. The Key should be Item 0, the type String and the Value BNRApplicationAssembly.
Run the application again, as if by magic the Table View will now be populated with items from BNRItemStore. Now if you need to switch out the BNRItemStore or add other dependencies to View Controllers they can be managed using Typhoon.
Over the coming weeks I will be posting a lot more info on Dependency Injection using Typhoon. Future blog posts will build on the concepts within this post and give more real-world examples of eliminating common dependencies encountered within applications.