This post is an Objective-C adaptation of Do it your self Dependency Injection (Java) .The article is a very well done and comprehensive introduction for the manual DI. I find that it also explains in a simple and clear manner DI concepts. What I have done here is to apply the idea of manual DI for objective-C. For a better understanding of the DI concepts I recommend the resource section of this website.
The mini project which I am going to describe is an Objective-C iPhone project. As it uses blocks to implement providers, the compiler must support blocks. In iOS, blocks are supported starting with version 4. For previous versions of iOS plblocks must be downloaded and installed. (Note: It is possible to use other techniques to implement providers).
The following post is a step by step guide for almost trivial app. It is a Master-Details app with a persons list and a detail page. The complete source code of the example can be downloaded from (url to come....)
Application Scope
First step is the create the ApplicationScope class which will hold all objects with the lifetime of a singleton plus the configuration params.
1
2
3
4
5
The injector
The injector will evolve as the application grows and as more code gets written. To start with the following will suffice:
1 2 3 4 |
|
For the moment this simple implementation will work just fine:
1 2 3 4 5 | +(ListController*) injectListController:(AppScope*)appScope{ ListController * listController = return ; }; |
We can observe that the injector provides a bunch of class methods which take a scope object as a parameter. Each method is responsible of wiring up and providing a certain object.
Injector Bootstrap
In a DI application the injector gets wired once and forgotten. Normally we would like to bootstrap the injector as early as possible, ideally in main(). As the iphone application has a particular startup sequence and for this example we don't want to change it too much, I will bootstrap the injector in appdelegate's application:didFinishLaunchingWithOptions: This creates a couple of particularities if compared with a classical DI solution.
1 2 3 4 5 6 7 8 9 10 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { AppScope *appScope = ; ListController *listController = ; ; ; ; return YES; } |
The main xib file contains the main window and a main navigation controller. Usually main xib files are loaded by the framework. As the UINavigationController cannot be subclassed, I don't see any problem in leaving it inside the xib. A good practice is not to keep controller or model objects inside xib files ( even if it is technically possible ).

As seen in the code, a root controller object get's pushed in the navigation controller. This controller object is obtained from the injector :
|
and pushed it to the navigation controller:
|
There are a couple of important points about this method :
- at the time of injection our app delegate object is already living. In order to set up his dependencies we should use setter injection (self.property = [LCInjector injectProperty:appScope];
- we should forget our injector object and not keep references to it into the delegate.
- as always it is better if we can limit the role of the app's delegate object
- the app delegate object depends on the injector which makes it hard to test. If we limit the amount of work in application:didFinishLaunchingWithOptions: to an absolute minimum and if we limit the role of the delegate testing should become easy.
- last but not least, if we absolutely must have objects inside main xib, objects which are needed by the injector, we should pass them to the AppScope object constructor before injection.
How to inject a siple UIViewController based class
In this trivial example we will inject a simple view controller object. The initializer of the ListController will evolve as we discover more dependencies, but the following code is ready to use template for controller injection:
If we are using multiple views for the same controller (very rare case on iOS) we can also inject the xib name.
1 2 3 4 5 | +(ListController*) injectListController:(AppScope*)appScope{ ListController * listController = return ; }; |
If we are using multiple views for the same controller (very rare case on iOS) we can also inject the xib name.
Injecting an object's dependencies
For the example to work the list controller must display a collection of Persons objects. The re-factored initializer for ListControler, at this point is :
1 2 3 4 5 6 7 8 9 | -(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil persons:(NSArray*)personsArray{ self = ; if { persons_ = ; } return self; } |
As at the creation of ListController the injector must supply an array of persons, the injector needs a method which provides that:
1
2
3
4
5
6
7
8
+(NSArray*) injectPersons:(AppScope*)appScope{
Person *first,*seccond,*third;
first = ;
seccond = ;
third = ;
NSArray * persons = ;
return persons;
}
The new injectListController is :
1 2 3 4 5 6 7 8 | +(ListController*) injectListController:(AppScope*)appScope{ NSArray *personsArray = ; ListController * listController = ; return } |
On line [2] the dependency is obtained from the injector and passed to the initializer on line [6].
At this point, with a little extra code, the app looks like this :
Providers
When we click on one of the persons in this list, the app should display a detail page, where the user can eventually edit the person or view more details. To keep things simple I have created a viewcontroller which looks like this :
In order to support this behavior, the initializer of DetailController should take a Person as the argument:
1 2 3 4 5 6 7 8 9 | -(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil person:(Person*) aPersonOrNil{ self = ; if { person_ = ; } return self; } |
The DetailController displays the Person's details during ViewDidLoad:
1 2 3 4 5 | - (void)viewDidLoad { ; nameLabel_.text = person_.name; addressLabel_.text = person_.address; } |
The usual way to wire-up this detail page is inside ListControllers tableView:didSelectRowAtIndexPath:
1 2 3 4 5 6 7 8 9 10 |
|
The ListController creates a DetailController which displays the person's details. This means that the DetailController dependency is not properly injected by the injector. If we use the previous method to inject the DetailController inside ListController we will end up with one instance of DetailControler. The problem is that usually a new instance of DetailController is needed at each tap. The solution is to inject a provider of DetailControllers which gets called when the DetailController is needed. A simple provider implemented using blocks looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 | typedef DetailController* (^DetailControllerProvider)(Person*); +(DetailControllerProvider) injectDetailControllerProvider:(AppScope*)appScope{ DetailControllerProvider provider = ^DetailController*(Person* aPersonOrNil){ DetailController *controller = ; return ; }; return provider; } |
What it does is to return a block which takes a Person as parameter and returns a configured DetailController when invoked. It is worthwhile noting that the block is in the context of the injectDetailControllerProvider and it has access to appScope. It can use appScope object to inject other objets if needed. A more detailed example will follow soon.
Next step is to add a block instance variable(detailControllerProvider_) to ListController and accept the provider block as a constructor parameter:
1 2 3 4 5 6 7 8 9 10 |
|
The ListController initializer implementation looks like this :
1 2 3 4 5 6 7 8 9 10 11 | -(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil persons:(NSArray*)personsArray detailControllerProvider:(DetailController* (^)(Person*))provider{ self = ; if { persons_ = ; detailControllerProvider_ = ; } return self; } |
As the blocks follow memory management rules we retain the block and release it in dealloc. After reimplementation the ListControllers tableView:didSelectRowAtIndexPath: method is:
1 2 3 4 5 6 7 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { Person *selectedPerson = ; DetailController *detailController = detailControllerProvider_(selectedPerson); ; } |
Last step is the injector's injectListController to pass the provider as initialization parameter for ListController:
1 2 3 4 5 6 7 8 9 10 11 | +(ListController*) injectListController:(AppScope*)appScope{ NSArray *personsArray = ; DetailControllerProvider provider = ; ListController * listController = ; return ; } |
Hi, there is nice implementation of 'inversion of control' for iPhone at http://github.com/mivasi/Objective-IOC
ReplyDelete