Skip to content

Types of Injections

Jasper Blues edited this page Apr 27, 2015 · 118 revisions

In this section. . .


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];

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 collaborating TyphoonAssembly 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)];
    }];
}