Dependency injection and IoC in iOS and Swift

Sep 4, 2014 · Follow on Twitter and Mastodon

Inversion of Control and Dependency Injection are hot topics in .NET, but rare in the iOS community. They are however powerful tools, so let’s look at how to setup IoC in iOS.

UPDATE 2022 Today, alternatives like Dip and Swinject are available. I however stopped using an engine, which often requires bootstrapping, and instead have a static IoC class that lazily resolves dependencies in a more effective way.

For those of you who are new to Inversion of Control and Dependency Injection, let’s take a look at the world’s shortest acronym dictionary:

  • iOS = Apple’s Mobile Operating System
  • IoC = Inversion of Control
  • DI = Dependency Injection

This article targets those of you who know the concept of IoC and look for a way to get it to work in iOS. For those of you who are unfamiliar with the concept of IoC, apologize me for skipping over core concepts you may not know about.

Why I want IoC in iOS

Coming from .NET, I find IoC/DI to be far less embraced by the iOS community. There are several great C# frameworks for IoC & DI, making it a breeze to use it in .NET.

In the iOS community, my impression is however that (and sorry for being a jerk here) good software architecture is uncommon. I’ve seen so many projects having 99% of the code in AppDelegate, just because that is how Paul Hegarty does it in his iTunes U iOS course.

I also learned iOS through Paul, but was disappointed that he so often suggested putting so much code in AppDelegate. And sure enough, most iOS projects I have looked at have huge AppDelegate monoliths handling a lot of core logic, defining global methods, keeping global state etc. It’s a terrible way to write code.

Don’t get me wrong. I am deeply impressed by the many developers who manage to tweak the iOS platform in ways I could not, providing with amazing libraries. However, compared to the maturity level of the .NET community, the iOS community must step up the game.

How I build things

I prefer to base most functionality on protocols, then add implementations that can handle a task in various ways. This makes it easy to unit test, use fake implementations early on and replace implementations later at one single place, etc.

I prefer abstract types that can be easily replaced at one place, with dependencies being automatically resolved. I therefore want a clean way of implementing IoC in my iOS apps.

I want one place where I wire up the app, to make it easy to reconfigure it whenever I need to, and be certain that there are no side-effects, that my app still behaves as expected.

With this in mind, I think I have finally found a nice approach. I will first describe how I tried to solve these problems using an object factory, as well as the drawbacks of this approach.

Object factory, the first flawed approach

In iOS, I used to go with manually handled, static object factory classes. Every protocol I wanted to resolve had to be added to these classes, as such:

@interface ObjectFactory : NSObject

+ (id<AppNavigator>)getAppNavigator;
// etc. etc.

@end

A big drawback with this, is that it requires one method for each protocol. It’s also static (at least the approach above), which means that classes must rely on it.

Say hello to CoreMeta

When I decided to solve my IoC pains for real, I found a library called CoreMeta.

As you add CoreMeta to a project, you may run into some name collisions, since CoreMeta doesn’t prefix it’s classes, protocols and categories. This means that some CoreMeta files, like Container.h and NSString+Utilities.h may collide with your project.

With that said, you may have to tweak CoreMeta a bit to get it to work. It’s not a big hassle, but note that you may run into unexpected bugs when adding CoreMeta.

CoreMeta’s central class is Container (they should have named it CMContainer), which is used to register types then to resolve registered classes and protocol.

CoreMeta is smart enough to recursively resolve implementations. If you have two types (A and B) and A has a property of B, CoreMeta automatically resolves B when you resolve A.

How I put it all together

CoreMeta is nice, but I don’t want my entire app to know about it. I therefore use a general container protocol, then register CoreMeta’s Container as the type to use in my app. This makes it possible for my app to rely on a general container, and not directly on CoreMeta.

Some classes will not be resolved using the container. View controllers and views are often created by storyboards or xibs. Such types must access CoreMeta with minimum coupling.

With all this in mind, this is how I set it up.

1. Add CoreMeta to your app target

Since CoreMeta has a lot of protocols, classes and categories, you shouldn’t add it to any libraries you have, since that would cause CoreMeta dependencies to leak throughout the entire system. Instead, only add CoreMeta to your app target.

2. Create a container abstraction

To reduce coupling, add an IoC container protocol to your app project. Since the CoreMeta api is really nice, I just let the protocol reflect the parts I use:

@protocol IoCContainer <NSObject>

- (id)objectForKey:(NSString *) key;
- (id)objectForClass:(Class)classType;
- (id)objectForClass:(Class)classType cache:(BOOL)cache;
- (id)objectForClass:(Class)classType withPropertyValues:(NSDictionary *)dictionary;
- (id)objectForClass:(Class)classType usingInitSelector:(SEL)selector withArguments:(NSArray*)args;
- (id)objectForProtocol:(Protocol *)protocol;

@end

3. Make CoreMeta implement IoCContainer

In order to make it possible to register the CoreMeta Container class as the IoC container of choice, add the following to the Container class:

#import "IoCContainer.h"

@interface Container : NSObject<IoCContainer>

Since we used the Container class as template for our IoCContainer protocol, all required methods are already implemented. Other IoC frameworks may need addtional work.

4. Global IoC awareness

At the same level as the IoCContainer protocol, add an NSObject category that lets you register your IoCContainer implementation and retrieve the registered container.

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

@interface NSObject (IoCContainer)

@property (nonatomic, readonly) id<IoCContainer> ioc;

- (void)registerIoCContainer:(id<IoCContainer>)ioc;

@end
#import "NSObject+IoCContainer.h"

@implementation NSObject (IoCContainer)

static id<IoCContainer>_ioc;

- (id<IoCContainer>)ioc {
    return _ioc;
}

- (void)registerIoCContainer:(id<IoCContainer>)ioc {
    _ioc = ioc;
}

@end

To make the extension globally available, then add NSObject+IoCContainer.h to any prefix files you have. In Swift, add the reference to your bridging header.

In Swift, I replaced the category with a static IoC class that has a container property that gets and sets a static variable.

import Foundation

private var _container : IoCContainer?

class IoC {
    class var container: IoCContainer {
        get { return _container! }
        set { _container = newValue }
    }
}

5. IoC container registration

Now create a Bootstrapper class that bootstraps the application. The one below is written in Swift and uses the IoC.container approach instead of the IoC category:

import UIKit

class AppBootstrapper : NSObject {

    func bootstrap() {
        let container = Container.sharedContainer()
        IoC.container = container

        container.registerClass(MyClass.self, forProtocol:MyProtocol.self, cache:false)
    }
}

The class first registers the container we want to use, then registers everything needed to make the app work. Then call the method from your AppDelegate, as such:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
        AppBootstrapper().bootstrap()
        ...

This approach means that an IoC container will be registered when the app starts. For unit tests, you are free to register any container you want, at any time.

6. Resolve implementations through the container

We are now ready to try our IoC. Let’s set up a Theme protocol that can be implemented by classes that can affects navigation bar color, search bar appearance etc.

After registering the Theme protocol and implementation in the bootstrapper, AppDelegate just have to call the following at startup:

(ioc.objectForProtocol(Theme) as Theme).applyTheme()

The registered Theme implementation will be resolved, then properly applied.

Conclusion

That’s about it, hope this helps. It was written in quite a rush, so let me know if I missed some vital information, wrote some typos, have some incorrect info etc.