Objective-C dependency injection in 4 easy steps

In this article we’ll show you how to easily apply dependency injection (DI) in your code with Reliant, our open-source DI framework.

In a few easy steps you’ll learn how to set up a very simple ‘Hello World’ DI scenario and along the way we’ll explain why DI is actually kinda neat :).

You can either follow this tutorial from scratch or just build and explore the finished product, which you can find here.

Step 1: Importing Reliant

Let’s start by creating a new project and make it an ‘empty’ application. You know how to do this so let’s not go into detail here.

Thanks to CocoaPods this is as easy as creating a Podfile and adding the following lines:

pod 'Reliant'

Then run ...

pod install

Now close XCode, open the newly created .xcworkspace file and you’re done!

Step 2: Bootstrapping Reliant

We’ll need to configure Reliant in our project so that:

  • it gets loaded when the app starts
  • it uses our own configuration data (which classes should be injected, etc…)

Let’s do just that. So…

It needs to be loaded when the app starts

Let’s open up AppDelegate.m and modify it to look like this:

AppDelegate.m

#import "AppDelegate.h"
#import <Reliant/Reliant.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Initialize Reliant
    [self ocsBootstrapAndBindObjectContextWithConfiguratorFromClass:[AppConfiguration class]];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
// Rest of your AppDelegate file ...

What we did here is:

  • Import Reliant.h
  • Bootstrap Reliant: we tell Reliant to create a working context that uses our own custom configuration for dependency injection.

This configuration is something we’ll set up later. You’ll notice that this code doesn’t compile, which is perfectly normal since we did not yet create the AppConfiguration class yet.

It needs to use our configuration data

In the previously shown code block we used a ‘configuration’ class called AppConfiguration. Let’s create this class.

AppConfiguration.m

#import "AppConfiguration.h"

@implementation AppConfiguration

@end

This is a plain old Objective-C class. Nothing out of the ordinary. You’ll notice however that it does not really say or do anything. Once we start defining classes that should be injected throughout our project, this is where we’ll configure them.

For now, just make sure to import AppConfiguration.h in your AppDelegate so that your code compiles.

Step 3: Using Reliant

Note: For simplicity’s sake we are writing most of our code in the AppDelegate class. Of course, you would normally use Reliant to inject other classes such as your ViewControllers, Services, etc… We’re just trying to make this sample code as simple as possible.

Let’s create a simple Hello World case where we use a ‘Greeter’ service that is injected by Reliant.

But first: the implementation without Reliant

We create a ‘protocol’ (or interface) for our Greeter service.

Greeter.h

#import <Foundation/Foundation.h>

@protocol Greeter <NSObject>

- (NSString *)sayHelloTo:(NSString *)name;

@end

Then we create an implementation following this protocol.

FriendlyGreeter.h

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

@interface FriendlyGreeter : NSObject <Greeter>

@end

FriendlyGreeter.m

#import "FriendlyGreeter.h"

@implementation FriendlyGreeter

-(NSString *)sayHelloTo:(NSString *)name {
    return [NSString stringWithFormat:@"Hi, %@! How are you?", name];
}

@end

Long story short, friendly greeter is friendly. Yay! Now for the important part. Using this service in our AppDelegate means that we need to instantiate FriendlyGreeter.

AppDelegate.m

#import "AppDelegate.h"
#import "AppConfiguration.h"
#import <Reliant/Reliant.h>
#import "FriendlyGreeter.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Initialize Reliant
    [self ocsBootstrapAndBindObjectContextWithConfiguratorFromClass:[AppConfiguration class]];

    // Saying hello
    id<Greeter> greeter = [[FriendlyGreeter alloc] init];
    NSLog(@"Greeting: %@", [greeter sayHelloTo:@"dude"]);

    // Rest of AppDelegate.m ...

As you can see, we:

  • imported FriendlyGreeter.h
  • instantiated FriendlyGreeter and called sayHelloTo:

Running this code outputs the following in your console:

Greeting: Hi, dude! How are you?

And this works fine… However… In a perfect world, we wouldn’t need to have this hard dependency on FriendlyGreeter in our code. Instead, we would only need to use the protocol, Greeter. Well, this is exactly what dependency injection does for us. It injects classes we are depending on so that we do not need to hardcode instantiate them ourselves. It takes this control away from the ‘depending’ class and puts the control somewhere outside. In our case Reliant will take care of it. That is what Inversion of Control means. Tadaah!

So, without further ado: the implementation with Reliant

AppDelegate.h

#import <UIKit/UIKit.h>
#import "Greeter.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) id<Greeter> greeter;

@end

We create a greeter @property, using only the protocol and not mentioning the actual implementation. We’ll later tell Reliant to find this property and inject the correct implementation.

AppDelegate.m

#import "AppDelegate.h"
#import "AppConfiguration.h"
#import <Reliant/Reliant.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Initialize Reliant
    [self ocsBootstrapAndBindObjectContextWithConfiguratorFromClass:[AppConfiguration class]];

    // Saying hello
    NSLog(@"Greeting: %@", [self.greeter sayHelloTo:@"dude"]);

    // Rest of AppDelegat.m ...

We removed the hard dependency on FriendlyGreeter by:

  • removing the import statement
  • removing the init code

We now remain with only:

  • the greeter @property
  • a call to that property using only the protocol

We do not need to instantiate this property. Reliant does this for us. At least we’ll tell it do so with these final pieces of code:

AppConfiguration.m

#import "AppConfiguration.h"
#import "FriendlyGreeter.h"

@implementation AppConfiguration

- (id<Greeter>) createSingletonGreeter {
    return [[FriendlyGreeter alloc] init];
}

@end

We tell Reliant to create a singleton for Greeter and inject it in any Reliant enabled class (see [self ocsInject]) where a @property named greeter exists. As you see, it is only here that we explicitely instantiate an actual Greeter implementation, in this case FriendlyGreeter. Reliant uses this code (our configuration class) to discern which properties it should inject and how they should be instantiated. It uses the last part of the function name (createSingleton*THISPART*) to know the property name you are using throughout your code. Of course Reliant offers a lot more power such as aliases, eager loading, etc. but you can read up on this in Reliant’s excellent documentation. All that is left for us to do now is to tell Reliant to inject our DI-dependencies in our depending class, in this case the AppDelegate class. Just add one line:

<pre>[self ocsInject]</pre>

Our final AppDelegate implementation now looks like this: AppDelegate.m

#import "AppDelegate.h"
#import "AppConfiguration.h"
#import <Reliant/Reliant.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Initialize Reliant
    [self ocsBootstrapAndBindObjectContextWithConfiguratorFromClass:[AppConfiguration class]];

    // Perform Reliant injection on self
    [self ocsInject];

    // Saying hello
    NSLog(@"Greeting: %@", [self.greeter sayHelloTo:@"dude"]);

    // Rest of AppDelegate.m ...

That’s it! You now have a dependency injected implementation of your AppDelegate code.

Step 4: Profit!

Let’s say we would like to quickly change the Greeter implementation to UnfriendlyGreeter. All we need to do is change our Configuration class as such:

AppConfiguration.m

#import "AppConfiguration.h"
#import "UnfriendlyGreeter.h"

@implementation AppConfiguration

- (id) createSingletonGreeter {
    return [[UnfriendlyGreeter alloc] init];
}

@end

And presto! All instances of the greeter @property will now be injected with UnfriendlyGreeter instead.

You can probably see how Dependency Injection / Inversion of Control can be very convenient, for example in a unit testing scenario where you’d like your tested class to call a mock service instead of the real service. Just tell your test to inject the property with a mock and you’re done.