Dependency Injection for iOS
So what is dependency injection? And why should I care?
Dependency injection is just a fancy word for passing objects to methods instead of creating them inside those methods.
Suppose you have a simple UIViewController which displays a few items and when you tap a button, it reverses the order of the items and saves them to a file.
@interface ViewController () <UITableViewDataSource>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) NSArray *dataArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *fileContents = [NSArray arrayWithContentsOfFile:@"myfile"];
if (fileContents) {
self.dataArray = fileContents;
} else {
self.dataArray = @[@"First Item", @"Second Item", @"Third Item", @"Fourth Item"];
}
}
- (IBAction)reverseOrderAndSaveToFile:(UIButton *)sender {
self.dataArray = [[self.dataArray reverseObjectEnumerator] allObjects];
[self.dataArray writeToFile:@"myfile" atomically:YES];
[self.tableView reloadData];
}
@end
The code above should bother you on a deep level. Something is terribly wrong with it, and it's not that it's in Objective-C. So what is it?
Those nasty write to file commands are just noise. When you read a View Controller's source code do you want to find out how it's datasource works? Do you really care what the name of the file where data is stored is? No, you just skip that code, because you are interested in how views are controlled.
So let's comment out all the lines which deal with reading and writing data:
- (void)viewDidLoad {
[super viewDidLoad];
// NSArray *fileContents = [NSArray arrayWithContentsOfFile:@"myfile"];
// if (fileContents) {
// self.dataArray = fileContents;
// } else {
// self.dataArray = @[@"First Item", @"Second Item", @"Third Item", @"Fourth Item"];
// }
}
- (IBAction)reverseOrderAndSaveToFile:(UIButton *)sender {
self.dataArray = [[self.dataArray reverseObjectEnumerator] allObjects];
// [self.dataArray writeToFile:@"myfile" atomically:YES];
[self.tableView reloadData];
}
Ok, so far so good. So lets get rid of comments and use real code:
- (void)viewDidLoad {
[super viewDidLoad];
self.array = [self.fileStorage readDataFromFile];
}
- (IBAction)reverseOrderAndSaveToFile:(UIButton *)sender {
self.array = [[self.array reverseObjectEnumerator] allObjects];
[self.fileStorage writeArrayToFile:self.array];
[self.tableView reloadData];
}
What we just did is called class extraction. We extracted the file reading and writing into a new class. So how does that class look like?
@implementation FileStorage
- (NSArray *)readDataFromFile {
NSArray *fileContents = [NSArray arrayWithContentsOfFile:@"myfile"];
if (fileContents) {
return fileContents;
} else {
return @[@"First Item", @"Second Item", @"Third Item", @"Fourth Item"];
}
}
- (void)writeArrayToFile:(NSArray *)arrayToSave {
[arrayToSave writeToFile:@"myfile" atomically:YES];
}
@end
Oh, you see what the code bug did there? He just extracted that ugly, file specific code into a class.
So how does the ViewController get a pointer to the FileStorage object you ask? Well, through dependency injection. We literally inject, a dependency:
@implementation ViewController
- (instancetype)initWithFileStorage:(FileStorage *)storage {
self = [super init];
if (self) {
self.fileStorage = storage;
}
return self;
}
And that's what dependency injection is about. There are many ways to inject a dependency:
1. Constructor Injection - what we just saw
2. Setter Injection
3. Parameter Injection (fancy way of saying you pass an object to a method which it uses to do it's work).
So why go through all this pain, what do you gain?
The FileStorage class can be tested. You can write tests for writing and reading from file. This is extremely cool as you might have noticed saving to file won't really work now.
It is easier to read the View Controller source code because the details of writing and reading data are hidden away.
You can replace the FileStorage class implementation by saving to CoreData or to a plist file. The ViewController couldn't care less. And after you change the implementation of the FileStorage class, you have unit tests to test it still works.
One team member can work on the View Controller and another one can work on the FileStorage class. No need to wait for each other.
You can find the source code on GitHub.
What are the things you should inject into a ViewController?
You should inject things like Persistence, Networking, DataManipulation. The CodeBug goes as far as injecting an object called an Interactor into his ViewControllers. The interactor performs Requests, Data Fetching and Writing, Data Manipulation and formating. The view controller just receives formatted data and displays it. But more on that later.