I'm using Typhoon for Dependency Injection with iOS.
I have registered a class, ConfigProviderImpl
, that is depended upon by other classes. Basically, I want a method, loadConfig
to be called the first time that ConfigProvderImpl
is requested by another object.
I've used Dependency Injection in other languages and something like this could usually be solved by making the instance up front, calling the method, and then registering the instance itself with the container so that it was already in the right state. I'm using Typhoon 1.6.9 at the moment and it doesn't seem to have the ability to register instances. I've checked the wiki for 2.0 too and it still doesn't allow this.
At the moment I'm using:
[definition setBeforePropertyInjection:@selector(loadConfig)];
But this is being called every time the class is requested. I realise I could use some dispatch_once
magic but I just wondered if there was a cleaner way?
Dependency injection frameworks for other languages are often focused on server-side development. So the default scope for components is singleton. This makes sense, given that a server may need to execute the use-case for any one of its services at a given time.
However in desktop and mobile development, we're typically servicing one use-case at a time. And, particularly on mobile, we have memory constraints at play. Therefore, Typhoon introduces a new memory scope as default - TyphoonScopeObjectGraph. With this scope, any shared references during resolution of a component will return the same instance. This means that:
To create a lazy singleton:
Of course, while this is the default, not all components will require this behavior. To make a singleton that isn't instantiated until the first request for it:
definition.scope = TyphoonScopeSingleton;
definition.lazy = YES;
After doing this:
There are four scopes in total TyphoonScopeObjectGraph, TyphoonScopePrototype (always returns a new instance), TyphoonScopesingleton and TyphoonScopeWeakSingleton. Further explanation for each of these is available in the Typhoon docs.
Alternative Approach:
Your other approach was to register an object instance in the container. In Typhoon there's not a short-hand way to register a given instance to play some role. However you can inject any object, and you can register a component that will produce other components in the assembly. Therefore we can do the following:
- (id)configProvider
{
return [TyphoonDefinition withClass:[ConfigProvider class]
configuration:^(TyphoonDefinition* definition){
//Typhoon 1.x was more explicit (withDefintion, withBool, withInstance, etc)
//in 2.0 you just use the word 'with'
[definition injectProperty:@selector(config) with:someObject];
}];
}
- (id)config
{
return [TyphoonDefinition withFactory:[self configProvider]
selector:@selector(config)];
//You can also have args here, eg @selector(configForEnv:)
}
The docs for this feature are here.