How To Add Dependency Injection

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 Application DependenciesFlurry 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

 

Single View Application

Fill out the details as follows:

Product Name: DependencyInjectionSample
Language: Objective-C
Devices: iPhone

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:

pod init

 

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
end

inhibit_all_warnings!

 

Back in the Terminal window run pod install

Pod Installation Animation

 

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.

Application Overview Diagram

 

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.

Objective-C File

Enter the following details in the next screen and once done, click Next and Create the file.

File: BNRItemStore.h

The BNRItemStore.h is going to be our protocol class. Open up the BNRItemStore.h file and edit as follows:

@class BNRItem;


@protocol BNRItemStore <NSObject>


/* ============================================================ Properties ========================================================= */
@property (nonatomic, readonly, copy) NSArray *allItems;


/* ============================================================ Singleton ========================================================= */
+ (instancetype)sharedStore;


/* ============================================================ Utility Methods =================================================== */
- (BNRItem *)createItem;
- (NSArray *)allItems;

@end

 

Right-click on the Store group and select New File. Select Cocoa Touch Class and click Next.

Cocoa Touch Class

 

Enter the following details in the next screen and once done, click Next and Create the file.

Class: BNRItemStoreImpl
Subclass of: NSObject
Language: Objective-C

Open up the BNRItemStoreImpl.h file and edit as follows:

#import <Foundation/Foundation.h>
#import "BNRItemStore.h"


@interface BNRItemStoreImpl : NSObject <BNRItemStore>



@end

 

Open up the BNRItemStoreImpl.m file and edit as follows:

#import "BNRItemStoreImpl.h"
#import "BNRItem.h"

@interface BNRItemStoreImpl ()


/* ============================================================ Properties ========================================================= */
@property (nonatomic) NSMutableArray *privateItems;

@end


@implementation BNRItemStoreImpl


/* ============================================================ Initializers ========================================================= */
- (instancetype)init {
  [NSException raise:@"Singleton" format:@"Use + [BNRItemStore sharedStore]"];
  return nil;
}

- (instancetype)initPrivate {
  self = [super init];
  if (self) {
    _privateItems = [[NSMutableArray alloc] init];
  }
  return self;
}


/* ====================================================================================================================================== */
#pragma mark - Protocol Methods
+ (instancetype)sharedStore {
  static BNRItemStoreImpl *sharedStore;
  if (!sharedStore) {
    sharedStore = [[self alloc] initPrivate];
    for (int i = 0; i < 5; i++) {
      [sharedStore createItem];
    }
  }
  
  return sharedStore;
}

- (NSArray *)allItems {
  return [self.privateItems copy];
}

- (BNRItem *)createItem {
  BNRItem *item = [BNRItem randomItem];
  [self.privateItems addObject:item];
  return item;
}

@end

 Creating the Item 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 Model.

Right-click on the Model group and select New File. SelectCocoa Touch Class and click Next.

Cocoa Touch Class

Enter the following details in the next screen and once done, click Next and Create the file.

Class: BNRItem
Subclass of: NSObject
Language: Objective-C

Open up BNRItem.h file and edit as follows:

#import <Foundation/Foundation.h>

@interface BNRItem : NSObject


/* ============================================================ Properties =============================================================== */
@property (nonatomic, strong) BNRItem *containedItem;
@property (nonatomic, weak) BNRItem *container;
@property (nonatomic, copy) NSString *itemName;
@property (nonatomic, copy) NSString *serialNumber;
@property (nonatomic) int valueInDollars;
@property (nonatomic, readonly, strong) NSDate *dateCreated;


/* ============================================================ Initializers ============================================================ */
- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber;

- (instancetype)initWithItemName:(NSString *)name;


/* ============================================================ Interface Methods ======================================================= */
+ (instancetype)randomItem;

@end

 

Open up BNRItem.m and edit as follows:

#import "BNRItem.h"

@implementation BNRItem


/* ============================================================ Initializers ============================================================ */
- (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber {
  self = [super init];
  
  if (self) {
    _itemName = name;
    _serialNumber = sNumber;
    _valueInDollars = value;
    _dateCreated = [[NSDate alloc] init];
  }
  return self;
}

- (instancetype)initWithItemName:(NSString *)name {
  return [self initWithItemName:name valueInDollars:0 serialNumber:@""];
}

- (instancetype)init {
  return [self initWithItemName:@"Item"];
}


/* ============================================================ Utility Methods ========================================================= */
- (NSString *)description {
  NSString *descriptionString = [[NSString alloc] initWithFormat:@"%@ (%@): Worth $%d, recorded on %@", self.itemName, self.serialNumber, self.valueInDollars, self.dateCreated];
  return descriptionString;
}


/* ============================================================ Interface Methods ======================================================= */
+ (instancetype)randomItem {
  NSArray *randomAdjectiveList = @[@"Fluffy", @"Rusty", @"Shiny"];
  NSArray *randomNounList = @[@"Bear", @"Spork", @"Mac"];
  
  NSInteger adjectiveIndex = arc4random() % [randomAdjectiveList count];
  NSInteger nounIndex = arc4random() % [randomNounList count];
  
  NSString *randomName = [NSString stringWithFormat:@"%@ %@", randomAdjectiveList[adjectiveIndex], randomNounList[
                          nounIndex]];
  int randomValue = arc4random() % 100;
  NSString *randomSerialNumber = [NSString stringWithFormat:@"%c%c%c%c%c", '0' + arc4random() % 10, 'A' + arc4random() % 26, '0' + arc4random() % 10, 'A' + arc4random() % 26, '0' + arc4random() % 10];
  
  BNRItem *newItem = [[self alloc] initWithItemName:randomName valueInDollars:randomValue serialNumber:randomSerialNumber];
  return newItem;
}

@end

 Setting up the user interface

Within the Project Navigator (⌘ + 3) select ViewController.h and hit enter on the keyboard and rename the file to BNItemsViewController. Do the same to the ViewController.m file.

Open up BNRItemsViewController.h and edit as follows:

#import <UIKit/UIKit.h>

@protocol BNRItemStore;


@interface BNRItemsViewController : UITableViewController


/* ===================================================== Properties ===================================================================== */
@property (nonatomic, strong) id <BNRItemStore> itemStore;


/* ====================================================================================================================================== */
#pragma mark - Initialization & Destruction


@end

 

Open up BNRItemsViewController.m and edit as follows:

#import "BNRItemsViewController.h"
#import "BNRItemStoreImpl.h"
#import "BNRItem.h"


@interface BNRItemsViewController ()

@end


@implementation BNRItemsViewController


/* ============================================================ Initializers ============================================================ */
- (instancetype)initWithStyle:(UITableViewStyle)style {
  return [self init];
}

- (id)init
{
  self = [super init];
  if (self) {
  }
  return self;
}



/* ============================================================ UITableViewDataSource ========================================================= */
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  return [[self.itemStore allItems] count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"];
  
  NSArray *items = [self.itemStore allItems];
  BNRItem *item = items[indexPath.row];
  
  cell.textLabel.text = [item description];
  
  return cell;
}

@end

 

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.

Blank Screen Animation

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.

Class: BNRApplicationAssembly
Subclass of: TyphoonAssembly
Language: Objective-C

Open up BNRApplicationAssembly.h file and edit as follows:

#import "TyphoonAssembly.h"

@class BNRItemsViewController;



@interface BNRApplicationAssembly : TyphoonAssembly


- (BNRItemsViewController *)itemsViewController;


@end

 

Open up BNRApplicationAssembly.m and edit as follows:

#import "BNRApplicationAssembly.h"
#import "BNRItemStoreImpl.h"
#import "BNRItemsViewController.h"


@implementation BNRApplicationAssembly


#pragma mark - Bootstrapping
/* ====================================================================================================================================== */
- (BNRItemsViewController *)itemsViewController {
  return [TyphoonDefinition withClass:[BNRItemsViewController class] configuration:^(TyphoonDefinition *definition) {
    [definition injectProperty:@selector(itemStore) with:[self store]];
  }];
}

- (id<BNRItemStore>)store {
  return [TyphoonDefinition withClass:[BNRItemStoreImpl class] configuration:^(TyphoonDefinition *definition) {
    [definition useInitializer:@selector(sharedStore) parameters:^(TyphoonMethod *initializer) {
      definition.scope = TyphoonScopeLazySingleton;
    }];
  }];
}


@end

 

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.

 

What’s next?

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.

1 reply
  1. Natalia
    Natalia says:

    Yeah, I’ve been bitten by Garbage Collection in the past. It made me very gushny of it. Plus, I have quite a comfort-level with retain-counting, so I was pretty skeptical of GC until fairly recently myself.IMHO, the situation has changed. The now-open source Obj-C garbage collector is solid and works well. I haven’t used it too much myself, to be perfectly honest (I was just starting to be convinced of it when I made the jump to iPhone stuff full-time) but everything I’ve done with it recently has worked perfectly, so it appears to me that the problems have been mostly worked out and the GC is ready for prime time. I’ve also been encouraged to use GC by several people whose opinions I trust, including several engineers from inside Apple. Simply put, GC is now faster than non-GC (something I didn’t believe, but I’ve seen the benchmarks) and the disparity will grow exponentially with OpenCL and Snow Leopard since Garbage Collection can happen outside of the main thread, using available cores or processors.Perhaps I should have phrased that differently – instead of “you should be using GC”, perhaps “Apple is encouraging developers to move towards GC” would have been better. Of course, very few things are black-and-white, but from the things I saw at the “What’s coming…” sessions at WWDC last year, I remain firmly of the opinion that Cocoa / Mac developers should be moving toward GC quickly, if not using it now. Of course, there are considerations like backwards compatibility with hardware and with using existing non-GC code, so my blanket statement was probably not the wisest choice of words.As for your second comment, it is true that specifying retain is a no-op in a GC environment so you can get away without doing this. However the docs say you should use assign when using GC, optionally specifying __weak or __strong refs. I haven’t used the optional terms in the example here, but by defining different values for the two environments, I have the flexibility to use the strong or weak hints without messing up the iPhone compile, simply by changing the #define or turning it into a macro.

    Reply

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *