Reputation: 1428
I see the benefit of passing dependencies between view controllers (injecting) as opposed to using global state. I'm curious, however, how people implement this in practice.
In the simplest case it is easy to pass a single model between two view controllers:
MyViewController *vc = [[MyViewController alloc] init];
vc.model = model;
[self.navigationController pushViewController:vc animated:YES];
I see two problems with scaling this approach however:
1) You may have a few services that are required by all view controllers (location manager, object stores, etc.) You therefore end up with a list of dependencies you have to remember to set for each of your view controllers:
MyViewController *vc = [[MyViewController alloc] init];
vc.model = model;
vc.locationManager = locationManager;
vc.objectStore = objectStore;
...
[self.navigationController pushViewController:vc animated:YES];
2) The second problem relates to the first problem: you're not actually enforcing that these dependencies be set before pushing the view controller. I suppose you could write an init method that requires all the dependencies but you still can't enforce it. Its also going to be long-winded and if you want to add a dependency later, its a huge pain.
What are the approaches in use to handle these issues? It doesn't seem like many Obj-C folks use dependency injection frameworks. One method I can think of is to create an AppContext class that contains all the shared dependencies and then pass that around to all your view controllers.
Also, in general you declare dependencies (in Java at least) with interfaces instead of implementations so you can mock them for unit testing. I don't see many Obj-C folks using protocols in this way. How do you then mock dependencies for unit testing?
Upvotes: 2
Views: 1077
Reputation: 400
Objection may be the answer you are looking for. It is a dependency injection framework that provides an alternative to manual dependency injection.
For example,
@implementation MyViewController
objection_register(MyViewController)
objection_initializer(initWithNibName:bundle:, @"MyNibName")
objection_requires(@"model", @"locationManager", @"objectStore", @"objectFactory")
@synthesize model;
@synthesize locationManager;
@synthesize objectStore;
@synthesize objectFactory;
@end
Where we initialize it using an injector (See the guide for more information):
MyViewController *controller = [self.objectFactory getObject:[MyViewController class]];
[self.navigationController pushViewController:controller animated:YES];
Objection favors using property injection over 'constructor' injection. Primarily because Objective-C doesn't have constructors as apart of the language (it uses alloc init
as a convention). The Objective-C runtime provides little type information about a messages' parameters. However, the runtime provides a wealth of information about properties and the Key-Value Coding API is powerful and generally safer than NSInvocation.
Upvotes: 1
Reputation: 1166
The KEY to dependency injection is all hard dependencies should be specified in the constructor!
Your constructor should thus look like this:
- (id)initWithModel:(Model *)model locationManager:(LocationManager *)locationManager objectStore:(ObjectStore *)objectStore;
Anything that isn't optional and provides facility that the class itself should not be responsible for constructing should be specified in the constructor. This is also a huge aid in testing, as the object itself can be fed mock versions of it's dependencies for isolation of network sockets, storage backends, etc...
If this seems cumbersome and you feel like the constructor looks too big or ugly, this isn't because these should be properties or implicit dependencies! It probably means that the class is trying to do too much, or it's dependencies can be factored into composite objects.
I work on a very large (100,000 line) production iPhone application and am very adamant about test-driven development. The means by which I achieve dependency injection is through protocols, actually. You are totally right in your observation that very little sample code you see on the internet is written in this way, but it is 100% correct and appropriate.
A really useful pattern that I use all the time is having two constructors, one with all of the dependencies exposed, and one with less constructor parameters, and implicit defaults (for use in production).
For example:
// Fully exposed constructor, for easy unit testing.
- (id)initWithHost:(NSString *)host storageProvider:(id<StorageProvider>)storageProvider socketFactory:(id<SocketFactory>)socketFactory;
// Constructor that calls the former, with fully-functional defaults passed into former constructor implicitly.
- (id)initWithHost;
This pattern, combined with a good mocking framework (OCMockito or OCMock are both good) will take you very far in designing clean, honest and highly testable code. :)
Upvotes: 2