IoC in iOS
Published 04 Sep 2014
UPDATE 2017-01-25: Since I wrote this post, much better alternatives are now available for Swift. My favorite is Dip. A second option is Swinject. I really like Dip, but you should check out both and see which suits you best.
UPDATE 2015-01-25: As I moved from Objective-C to Swift and continued to use
CoreMeta, received segmentation fault when archiving my apps. After some digging,
I found out that it was because my IoCContainer protocol (that I use to make the
choice of IoC contaner abstract) did have
instancetype as return type. Changing
id solved the problem. I have updated this post with these adjustments.
IoC in IOS…wow, trying to discuss this with a friend who is new to iOS and did not know about the concept of IOC quickly became confusing!
So to avoid any confusion, first let’s define the world’s shortest dictionary:
- iOS = Apple’s Mobile Operating System
- IoC = Inversion of Control
This post targets those of you who understand the concept of IoC and are looking for a way to get it to work in iOS. For those of you who are unfamiliar with the concept of IoC, please do read on, but apologize me for skipping over things you may not know about.
The approach below works well in both Objective C and Swift.
I will first describe my thoughts on system architecture. If you are interested in the IoC solution only, please skip ahead.
Why I want IoC in iOS (dick head alert)
Being a C# developer at heart, my impression is that the concept of IoC/DI is far less known about and even less embraced by the iOS community than in more system development-oriented contexts. There are several great C# frameworks for IoC and DI, making it a breeze to setup on the .NET stack.
In iOS, though, my impression is that (and sorry for being a dick here) projects are most often hacks, with good software architecture being fairly uncommon. The fact that many developers jump on iOS development without any prior knowledge of how to build great systems, means that they have never had to think about these challenges…and end up with 99% of their code base in AppDelegate, just because that is how Paul Hegarty sets up critical code in the iTunes U iOS course.
Because…I also learned iOS through Paul Hegarty’s iTunes U iOS course. However, I was really disappointed that he so often suggested putting code in AppDelegate. And sure enough, most iOS projects I have looked at have their AppDelegates being huge monoliths, handling a lot of the app’s core logic, defining global methods, keeping global state etc. It’s nothing less than a terrible way of writing code.
Now, 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 us with amazing libraries. However, compared to the maturity level of the .NET community, the iOS community should start discussing these topics more often.
How I build things
I prefer to base most functionality on protocols. I then add implementations that can handle the task in various ways. This approach makes it easy to unit test the system, use fake implementations early on and replace implementations later on at one single place etc. etc.
I prefer abstract components that can be easily replaced at one place, with each dependency being automatically resolved. I have therefore looked for 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 the app whenever I need to, and be certain that there are zero side-effects, that all parts of my app still behave correctly.
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. If you are just interested in the IoC solution, please skip this section.
Object factory, the first flawed approach
Starting out with 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:
The big drawback with this approach, is that it is app-specific and that requires one method for each protocol that I want to be able to resolve. It is also static (at least the approach above), which means that classes must rely on it.
The object factory pattern also works bad for library code, since components in a shared library may come to require the existence of an object factory, which has to be defined in app scope, leading to dependencies pointing out of the core lib.
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 your project, you may run into some name collisions, since CoreMeta does not prefix it’s classes, protocols and categories. This means that some CoreMeta files, like Container.h and NSString+Utilities.h, may collide with your own. It also includes multiple files that it does not use.
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 (here, for instance, CoreMeta should have
named it CMContainer or anything but the very general name Container), which is
used to register implementations in various ways, then used to resolve registered
classes and protocol.
CoreMeta is also smart enough to recursively resolve implementations. This means that if you have two protocols (A and B) and B has a property of type A, CoreMeta will automatically resolve A when you retrieve a B instance through the container.
How I put it all together
CoreMeta is great, but I do not want my entire app to know about it. I therefore use a protocol that describes a container, then register CoreMeta’s container as the container I want to use in my app. This makes it possible for my app to rely on a general IoC container, rather than any specific implementation.
Furthermore, some classes will not be resolved using the container. For instance, view controllers and views are often created by a storyboards or xibs. For these classes, I want to be able to access the container with minimum coupling.
With all this in mind, this is how I set it up.
1. Add CoreMeta to your app project
Since CoreMeta is a library with a lot of protocols, classes and categories, you should not add it to any libraries you may have, since that would cause CoreMeta dependencies to leak throughout the entire system. Instead, add CoreMeta to your app’s project.
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:
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:
Since we used the Container class as template for our IoCContainer protocol, all required methods are already implemented. If you use another IoC framework, you may have to create a facade class that implements the IoCContainer protocol.
4. Global IoC awareness
At the same level as the IoCContainer protocol (e.g. the base library), also add an NSObject category that lets you register your IoCContainer implementation and retrieve the registered container.
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.
Note: In Swift, however, I have replaced the category with a static IoC class that has a container property that gets and sets a static variable.
5. IoC container registration
Next to the AppDelegate class, create a Bootstrapper class that is responsible to bootstrap the application. This one is written in Swift and uses the IoC.container approach instead of the IoC category:
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:
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 finally ready to try our IoC in action.
For instance, let’s use 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, the app delegate just have to call the following at startup:
The registered Theme implementation will be resolved, then properly applied.
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.