Typhoon! (www.typhoonframework.org)
A new dependency injection container for Objective-C. Light-weight, yet full-featured and super-easy to use.
- Read the User Guide or API Docs
- Try the sample application (Now updated for iOS7!!!!)
- Check the feature list.
otherwise. . .
Many people have trouble getting the hang of dependency injection, at first. And I think part of the problem is that it is actually so simple that we're inclined to look for something more complicated. "Surely that there has to be more to it?!", so to say.
So, with that in mind, imagine that you're writing an app that gives weather reports. You need a cloud-service (excuse the pun ;) ) to provide the data, and at first you go for a free weather report provider, but in future you'd like to integrate a weather service with better accuracy and more features. So, as do all good object-oriented developers, you make a WeatherClient protocol and back it initially with an implementation based on the free, online data provider.
Without dependency injection, you might have a View Controller like this:
-(id) init
{
self = [super init];
if (self)
{
//The class using some collaborating class builds its own assistant.
//it might be one of several classes using the weatherClient.
_weatherClient = [[GoogleWeatherClientImpl alloc] initWithParameters:xyz];
}
return self;
}
The thing with this approach is, if you wanted to change to another weather client implementation you'd have to go and find all the places in your code that use the old one, and move them over to the new one. Each time, making sure to pass in the correct initialization parameters.
A very common approach is to have a centrally configured singleton:
_weatherClient = [GoogleWeatherClient sharedInstance];
With either of the above approaches, in order to test your view controller, you now have to test its collaborating class (the weather client) at the same time, and this can get tricky, especially as your application gets more complex. Imagine testing Class A, depends on Class B, depends on Class C, depends on .... Not much fun!
Sure, you could patch out the singleton with a mock or a stub, but this requires peeking inside the code to find the dependencies. Besides taking time that could be better spent else-where, this ends up becoming "glass-box" testing as opposed to "black-box" testing. Isn't it better to be able to test the external interface to a class, without having worry about what's going on inside? And you have to remember un-patch again at the end of the test-case or risk strange breakages to other tests, where its difficult to pin-point what the real problem is might be. . .
. . . So with dependency injection, rather than having objects make their own collaborators, we have them supplied to the class instance via an initializer or property setter.
And now, it simply becomes:
-(id) initWithWeatherClient:(id<WeatherClient>)weatherClient
{
self = [super init];
if (self)
{
_weatherClient = weatherClient;
}
return self;
}
####Is that all they mean by 'injected'? Yes it is. Right now, you might be thinking "Geez! That's a pretty fancy name for something so plain." Well, you'd be right. But let's look at the implications on our application architecture: If you do this with significant collaborators throughout your app, it means that the GoogleWeatherClientImpl is now declared in a single place - the top-level assembly, so-to-speak. And all of the classes that need to use some kind of id<WeatherClient> will have it passed in. This means that:
- If you want to change from one implementation to another, you need only change a single declaration.
- Classes are easier to test, because we can supply simple mocks and stubs in place of concrete collaborators. Or the real collaborators, but configured to be used in a test scenario. (One of my design goals).
- It promotes separation of concerns and a clear contract between classes. Its easy to see what each class needs in order to do its job.
- Your app is easier to maintain and can accommodate new requirements.
If you proceed with the Dependency Injection pattern (assuming you're not one of the remaining "flat-earthers", who believe that Objective-C somehow magically alleviates the need for common-sense: "Oh, I don't do DI, I use swizzling class-clusters!"), then there are basically two options:
-
You can do dependency injection without a framework/library/container to help you. It is simple after all, and in fact I recommend you do this, at least as an exercise in software design. And yes, it is certainly possble that this will be adequate. But, I think its good to have help, if you can get it. You can also write tests without a test framework, mocks with out a mock library, software without a compiler.
-
So, going down the library/framework route, there's been quite a lot of action in Objective-C land, over the last three years. In fact, there are now around 15 Dependency Injection frameworks, many following in the footsteps of Google Guice. The authors have done a great job (Objection is especially good). However, I wanted an approach that allows the following:
-
Non-invasive. No macros or XML required. . . while Spring-style XML or Guice-style macros are fully supported (and sometimes useful), the main focus is on a powerful Objective-C runtime approach.
-
Its not necessary to change any of your classes to use the framework. Can be introduced into legacy applications.
-
No magic strings - supports IDE refactoring, code-completion and compile-time checking.
-
Provides full-modularization and encapsulation of configuration - grouping the application assembly details into a single document, with chapters. Let your architecture tell a story.
-
Dependencies declared in any order. (The order that makes sense to humans).
-
Makes it easy to have multiple configurations of the same base-class or protocol.
-
Allows both dependency injection (injection of classes defined in the DI context) as well as configuration management (values that get converted to the required type at runtime). Because this allows. . .
-
. . . ability to configure components for use in eg Test vs Production scenarios. This faciliates a good compromise between integration testing and pure unit testing. (Biggest testing bang-for-your-buck).
-
Supports injection of view controllers and storyboard integration.
-
Supports both initializer and property injection. In the case of the latter, it has customizable call-backs to ensure that the class is in the required state before and after properties are set.
-
Excellent support for circular dependencies.
-
Powerful memory management features. Provides pre-configured objects, without the memory overhead of singletons.
-
Lean. It has a very low footprint, so is appropriate for CPU and memory constrained devices.
-
Battle-tested - used in all kinds of Appstore-featured apps.
And then:
The following reports are published by our build server after each commit. Note that the status of the CI build is not related to tagged releases that are published and pushed to CocoaPods - these are stable.
Test Failures typically indicate a bug that has been flagged, but not yet fixed. By policy we maintain more than 90% test coverage.
(These reports are hosted on a temporary server until we fully complete moving our build-system to Gradle)
. . . are very welcome.
-
Look at, and contribute to the roadmap here.
I need help because | Action |
---|---|
I'm not sure how to do [xyz] | Typhoon users and contributors monitor the Typhoon tag on Stack Overflow. Chances are your question can be answered there. |
This looks like a bug | Please raise a GitHub issue |
I'll take all the help I can get | While Typhoon is free, open-source and volunteer based, if you're interested in professional consultation/support we do maintain a list of experts and companies that can provide services. Get in touch with us, and we'll do our best to connect you. |
- Jasper Blues (Founder / Project Lead) - [email protected]
- Robert Gilliam - [email protected]
- Daniel Rodríguez Troitiño
- Erik Sundin
- Aleksey Garbarev
- César Estébanez Tascón : Circular Dependencies fixes.
- Bryn Cooke : Late injections & story board integration.
- Jeffrey Roberts, Mobile Software Engineer at Riot Games, previous contributor to Swiz for ActionScript : Advice, feedback and testing.
- Jetbrains, maker of very cool software development tools : Assistance with AppCode integration.
- José González Gómez, Mobile and cloud developer at OPEN input : Feedback; testing, support for property placeholders in initializers.
- Sergio Padrino Recio : Performance improvements.
- Josh Ervine : Feedback and support for TyphoonFactoryProviders.
- Your name here!!!!!!!
(If you've sent a pull request and didn't get a mention. . . sorry! Please let us know and we'll correct it).
Here's a few apps built with Typhoon:
- Mod Productions' ACO Virtual iPad controller and companion AR app. (Awarded AppStore Best New Apps).
- GonnaGo - a social travel app.
- Alpify - don't go skiing or snow boarding without it!
- Many others. . . Your app here!!!
Apache License, Version 2.0, January 2004, http://www.apache.org/licenses/
© 2012 - 2014 Jasper Blues and contributors.