Mature iOS Development 3 - iOS Dependency Injection

This is the third article in a series of blog posts on tips to improve your iOS development process.

What is dependency injection?

Summary

An object that has dependencies on another object should be ignorant about the implementation of that object. It should only care about what it does, but not how it does it. It should also not be responsible for the dependent object’s life cycle.

Languages like Java and C# are very mature about dependency injections. In a few cases, the language itself provides interfaces (or protocols in Objective-C) that objects should implement. That makes it extremely easy to switch implementation or libraries, as long as they are coded to the same interfaces. It wouldn’t require any code changes.

Why should I use it?

  • Easier development: Once setup, it can make your development much easier and less verbose. Abstracting the life cycle of an object to the dependency injection framework usually results in having that dependency in the container object only need 1 line.
  • Improved testing: By injecting dependencies in another object instead of having it initialize the object itself inside of it, you can switch the injected object on different circumstances, like testing. For example, a controller could have a dependency on a object that handles all the network calls to your api. When running the app on a regular environment, you would inject your regular service object that would make those calls, but when running the test target you could have a different object that implements the same interface, but simply stubs those networks calls and returns hard coded responses.

Programming to the Protocol (or Interface)

One of the things to keep in mind if you want to use dependency injection on your code is that when your object has a dependency on another object, it’s only aware of its interface, not its implementation. This is very clear when you use the delegation pattern, where an object is depending on the delegate implementing a certain interface or protocol, but that object’s implementation is not known until runtime. The term “Programming to the Interface” (Interface in Java is what Protocols are for in Objective-C) is used to describe when dependencies of your objects are always protocols, so the implementations can be switched without the container object being affected.

See the example to understand the difference:

Not programming to the Protocol

Let’s say we define a UserService object, that handles loading a list of users from a data source.

Class Interface

@interface UserService : NSObject

- (NSArray*)listOfUsers;

@end

Class Implementation

@implementation UserService
  
- (NSArray *)listOfUsers {
  //Make call to Core Data, or API etc.
  return @[]; //For the example, we are just returning an empty array
}

@end

A controller depending on the UserService

@interface ViewController ()

@property (strong, nonatomic) UserService *userService;

@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //Initialize service and make call to load users    
    NSArray *myUsers = [self.userService listOfUsers];
}

@end

This code still works great, however, there’s no way to switch implementations of the UserService. You have a dependency on the UserService with its implementation.

Programming to the Protocol

We create a protocol with the same interface as the UserService example shown above:

@protocol UserService <NSObject>

- (NSArray*)listOfUsers;

@end

and create an object that implements that protocol.

The interface.

@interface MyUserService : NSObject <UserService>

@end
@implementation MyUserService

- (NSArray *)listOfUsers {
  //Make call to Core Data, or API etc.
  return @[]; //For the example, we are just returning an empty array
}

@end

ps.: You might have to come with a naming strategy for your protocol and object implementations, since they can’t share the same name. I’ve seen different approaches, like adding protocol or implementation to the name. The options would be:

Protocol Name Class Name
UserService UserServiceProtocol
UserServiceImplementation UserService

And the controller using that object

@interface ViewController ()

@property (strong, nonatomic) id<UserService> userService;

@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //Initialize service and make call to load users
    NSArray *myUsers = [self.userService listOfUsers];
}

@end

Both examples above will yield the same result, but the second will allow you to switch the implementation of the UserService to different objects.

Using Objection

There isn’t a wide variety of dependency injection libraries to choose on iOS. The one I’ve been using in my most recent projects is Objection, and I will use it this blog as example. I won’t describe how to install as it is well documented on Objections website, and it is also available to install through CocoaPods.

Objection makes it really simple to define dependencies and inject them; the setup is minimal, and very light weight. I’m going to use the examples above and change that to use Objection to have the dependency injected.

Just as a quick example, if you want to inject a dependency into an object, you can simply tell which properties in an objection should be injected using objection_requires(@”myObject”) inside the code implementation. Once an injector tries to inject that object, it will look for the type of that property being injected, initialize the object, and inject it in the property. By default it will call the -init method, but you can create custom initializers.

If I wanted to inject a NSDateFormatter into my object, I could just do this:

@interface ViewController ()

@property (strong, nonatomic) NSDateFormatter *formatter;

@end

@implementation ViewController

objection_requires(@"formatter")

- (void)viewDidLoad {
    JSObjectionInjector *injector = [JSObjection createInjector];
    [injector injectDependencies:self];

    NSLog(@"date formatter: %@", self.formatter)
    [super viewDidLoad];
}

and after injectDependencies: is called, the properties I listed as required will be all initialized and injected. You can also setup a default injector somewhere (like AppDelegate) so you don’t have to initialize a new injector every time. That way, you just need to call

//Initialize once somewhere in your app, like AppDelegate
JSObjectionInjector *injector = [JSObjection createInjector];
[JSObjection setDefaultInjector:injector];

//On objects you want to be injected
[[JSObjection defaultInjector] injectDependencies:self];

on the objects that should be injected. Good methods to initialize or use the default injector are in -awakeFromNib for controllers, or overriding -init methods in other objects.

Injecting from Protocol

Injecting dependencies based on a protocol requires a few extra steps to setup. That happens because multiple objects can implement the same protocol, so it would be nearly impossible for a dependency injection library to determine which one is the most adequate.

When you get to that, or a more complex setup with objection, it’s good to create an objection module. The objection module is where you can put additional setup for your dependencies, like binding objects to certain class dependencies, custom initializers, and more. Modules are the best place to add configurations that can change based on a different build type or target. i.e.: You can setup a ProductionModule that inject full implemented classes that are used in production, and another module for testing that will inject different classes or stubbed objects for those same dependencies, as long as they implement the same protocols.

Let’s create a subclass of JSObjectionModule and configure it so every time we depend on UserService protocol we get an instance of MyUserService. We do that by implementing -configure method:

@implementation MyAppModule

- (void)configure {
    [self bind:[MyUserService class] toProtocol:@protocol(UserService)];
}

@end

Now, we create an injector based on that module. I will create that in the AppDelegate and set it as my default injector.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    JSObjectionInjector *injector = [JSObjection createInjector:[[MyAppModule alloc] init]];
    [JSObjection setDefaultInjector:injector];
    return YES;
}

Our view controller with an injected user service would look like that using objection:

@interface ViewController ()

@property (strong, nonatomic) id<UserService> userService;

@end


@implementation ViewController

objection_requires(@"userService")

- (void)viewDidLoad {
    //Inject all dependencies
    [[JSObjection defaultInjector] injectDependencies:self];
    
    //Check if we got the right instance type
    NSAssert([self.userService class] == [MyUserService class], @"userService should be of kind MyAppService");

    [super viewDidLoad];
    //Initialize service and make call to load users
    NSArray *myUsers = [self.userService listOfUsers];
}

@end

Note my assert that is making sure that userService property is really of kind MyUserService.

I hope you understand the goal of this approach. As you can see, ViewController is completely ignorant on which implementation of the UserService protocol is being used. In fact, your entire application can be ignorant about it with the exception of your module implementation. The idea is to create multiple modules, and use different ones per target or build configurations. Those different modules would to bind different classes depending on those configurations.

Other advantages of Objection

Here are some other advantages that I find on using Objection:

  • Easy setup: can be installed with cocoapods, and no major configuration to start using libraries. Just create an injector and start injecting objects. It’s even easier if you create your own module and set a default injector.
  • Less code: Lot of places in your code might need the same objects over and over, and they are usually initialized the same way. NSDateFormatters is a good example. You might have multiple places using it, and they all use the same dateFormat, and use the same initialization. Instead of doing every time you need to use it, you can simple specify in your module a custom initializer that also sets the dateFormat, and you can simply objection_require(@”dateFormatter”) from there on.
  • Singletons per injector: You can easily make “annotate” objects as singleton with objection_register_singleton(MyObject). That won’t exactly make the object a singleton, but the injector will make sure to always inject the same instance. If you use a different injector, it won’t be the same instance though.
  • Manage multiple modules: Most apps just need one module, but you can also use multiple modules or injectors at the same time. i.e.: you could create a protocol for your custom alerts, and inject different implementations on different parts of the code. That’s very useful with A/B testing.
  • Testing: Probably one of the main advantages. Some objects just don’t play well when running unit tests. They rely too heavily on UI or network calls. An example where Objection really helped me was implementing tests for a service that interacted with socket connections. I couldn’t find any library that helped me stub responses like there are some for http requests. What I did was implement a “fake socket” object that had the same interface as the socket library, and used a module that binded any class on my test target requiring the socket class to actually get an instance of my “fake socket”.