-
Notifications
You must be signed in to change notification settings - Fork 268
Types of Injections
In this section. . .
- Initializer / Class Method Injection
- Property Injection
- Method Injection
- Injection call-backs
- Injections with run-time arguments
- Injecting collections
- Factory Definitions
- Circular dependencies
- Abstract and Base Definitions
All injections are defined within one or more TyphoonAssembly sub-classes.
@interface YourApplicationAssembly : TyphoonAssembly
- (Knight *)knight
- (id<Quest>)quest;
//etc. . .
@end
Key Concept: Before [activation](Activating Assemblies) each method returns a TyphoonDefinition
. After activation we'll use the same interface to return built instances. You can declare the return type as the type being built (Objective-C) or AnyObject
(Swift).
#Initializer / Class Method Injection
(John Reid of http://qualitycoding.org, and others sometimes call this 'constructor injection')
- (Knight *)basicKnight
{
return [TyphoonDefinition withClass:[Knight class]
configuration:^(TyphoonDefinition* definition) {
[definition useInitializer:@selector(initWithQuest:)
parameters:^(TyphoonMethod *initializer) {
[initializer injectParameterWith:[self defaultQuest]];
}];
}];
}
- (id<Quest>)defaultQuest
{
return [TyphoonDefinition withClass:[CampaignQuest class]];
}
Injections can be specified as arguments to an initializer, eg 'initWithQuest:' or class method, eg 'knightWithQuest:'.
- Its possible to use an empty (no-args) creation method. Eg [Knight knight]
- If no initializer is specified, then [[alloc] init] is implied.
Initializer injections can also be mixed with the kinds that follow. . .
#Property Injection
- (Knight *)cavalryMan
{
return [TyphoonDefinition withClass:[CavalryMan class]
configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(quest) with:[self defaultQuest]];
[definition injectProperty:@selector(damselsRescued) with:@(12)];
}];
}
NB: Property injection can be done [by type](What can be Injected#by-type) or with Auto Injection macros.
#Method Injection
Methods with one or more parameters can be injected.
- (Knight *)knightWithMethodInjection
{
return [TyphoonDefinition withClass:[Knight class]
configuration:^(TyphoonDefinition *definition) {
[definition injectMethod:@selector(setQuest:andDamselsRescued:)
parameters:^(TyphoonMethod *method) {
[method injectParameterWith:[self defaultQuest]];
[method injectParameterWith:@321];
}];
}];
}
#Injection call-backs:
Typhoon allows you to specify a method to be called before and after injections.
//Useful for 3rd party frameworks, otherwise just use init
definition.performBeforeInjections = @selector(applyAppTheme)];
//Intializer injection has the advantage that we can assert required
//state before proceeding, while property injection doesn't. But we
//can work around this with the following:
definition.performAfterInjections = @selector(checkStateAfterBuilt)];
//Call-back methods can also have arguments, eg:
definition.performAfterInjections = @selector(registerWithSubscriber:)
parameters:^(TyphoonMethod *method) {
[method injectParameterWith:[self eventSubscriber];
}];
//. . this behaves just like a method injection that is guaranteed
//to be called last. Although, not that for ordinary method injections
//that order is also followed.
As an alternative to declaring the property injection methods in the assembly, if you're not worried about your class having a direct dependency on Typhoon, you can also do the following:
#import Typhoon.h
- (void)typhoonWillInject
{
}
- (void)typhoonDidInject
{
}
#Injection with Run-time Arguments
Run-time arguments allow defining a factory on the assembly interface. Here is an example:
- (UserDetailsController *)userDetailsControllerForUser:(User *)user
{
return [TyphoonDefinition withClass:[UserDetailsViewController class]
configuration:^(TyphoonDefinition *definition) {
[definition useInitializer:@selector(initWithPhotoService:user)
parameters:^(TyphoonMethod *initializer) {
[initializer injectParameterWith:[self photoService];
[initializer injectParameterWith:user];
}];
}];
}
We can obtain a UserDetailsViewController with both the static and runtime dependencies as follows:
User* aUser = self.selectedUser;
UserDetailsViewController* detailsController =
[assembly userDetailsControllerForUser:aUser];
- When to use runtime arguments.
- Runtime arguments can be propagated through to dependencies.
- Runtime arguments can be used with circular dependencies.
- Runtime arguments can be backed by a factory protocol.
Limitations:
- Run-time arguments must always be an object. Primitives are not possible, however they can be [wrapped into NSValue](wrap primitive values into NSValue).
- Run-time arguments must be passed in to your definitions exactly as-is. Its not possible to manipulate the argument by calling any methods on it.
- Reading about how typhoon assemblies work "under the hood" should give some insights into these limitations.
#Injecting Collections
Typhoon will inject collections - NSArray, NSSet, NSDictionary and their mutable counterparts using the following special treatment:
- References to other TyphoonDefinitions are resolved to the built instances.
-
self
or any other collaboratingTyphoonAssembly
will result in Typhoon [injecting itself](What can be Injected#injecting-typhoon-itself). - Everything else is passed through as is.
Example:
- (Knight *)knightWithCollections
{
return [TyphoonDefinition withClass:[CavalryMan class]
configuration:^(TyphoonDefinition *definition) {
[definition injectProperty:@selector(favoriteDamsels) with:@[
@"Mary",
@"Mary"
]];
[definition injectProperty:@selector(friends) with:
[NSSet setWithObjects:[self knight], [self anotherKnight], nil]];
[definition injectProperty:@selector(friendsDictionary) with:@{
@"knight" : [self knight],
@"anotherKnight" : [self anotherKnight]
}];
}];
}
See also: What can be injected
#Factory Definitions
Sometimes its necessary to register a definition that produces other definitions. For example a legacy singleton that produces objects you'd like to inject into other classes in your app.
- (SwordFactory *)swordFactory
{
return [TyphoonDefinition withClass:[SwordFactory class]];
}
- (Sword *)blueSword
{
return [TyphoonDefinition withFactory:[self swordFactory]
selector:@selector(swordWithSpecification:)
parameters:^(TyphoonMethod *factoryMethod) {
[factoryMethod injectParameterWith:@"blue"];
}];
}
#Circular Dependencies
Sometimes you wish to define objects that depend on each other. For example a ViewController that is injected with a view, and a View that is injected with the ViewController as a delegate.
Typhoon supports circular dependencies in properties and methods.
- (SettingsController *)appSettingsController
{
return [TyphoonDefinition withClass:[AppSettingsController class]
configuration:^(TyphoonDefinition* definition)
{
[definition useInitializer:@selector(initWithSoundManager:settingsView:)
parameters:^(TyphoonMethod* initializer)
{
[initializer injectParameterWith:[_kernel soundManager]];
[initializer injectParameterWith:[self appSettingsView]];
}];
[definition injectProperty:@selector(title) with:@"Settings"];
}];
}
- (SettingsView *)appSettingsView
{
return [TyphoonDefinition withClass:[AppSettingsView class]
configuration:^(TyphoonDefinition* definition)
{
[definition injectProperty:@selector(delegate) with:
[self appSettingsController]];
}];
}
#Abstract and Base Definitions
If you wish to encapsulate configuration that will be shared among a number of derived definitions, you can do so as follows.
Define the base definition:
- (ClientBase *)abstractClient
{
return [TyphoonDefinition withClass:[ClientBase class] configuration:^(TyphoonDefinition* definition)
{
[definition injectProperty:@selector(serviceUrl) with:[
NSURL URLWithString:@"https://zaps.com/serviceRequest"]];
[definition injectProperty:@selector(networkMonitor) with:([self internetMonitor])];
[definition injectProperty:@selector(allowInvalidSSLCertificates) with:@(YES)];
[definition injectProperty:@selector(logRequests) with:@(YES)];
[definition injectProperty:@selector(logResponses) with:@(YES)];
}];
}
And now derive from it as follows:
- (StoreClient *)storeClient
{
return [TyphoonDefinition withClass:[StoreClient class] configuration:^(TyphoonDefinition* definition)
{
definition.scope = TyphoonScopeWeakSingleton;
definition.parent = [self abstractClient];
//More config specific to the sub-class.
[definition injectProperty:@selector(registered) with:@(NO)];
}];
}
Something still not clear? How about posting a question on StackOverflow.
Get started in two minutes.
Get familiar with Typhoon.
- Types of Injections
- What can be Injected
- Auto-injection (Objective-C)
- Scopes
- Storyboards
- TyphoonLoadedView
- Activating Assemblies
Become a Typhoon expert.
For contributors or curious folks.