Skip to content

When to use runtime arguments

Hok Shun Poon edited this page Feb 28, 2015 · 9 revisions

Back to [Types of Injections](Types of Injections)


Typhoon allows defining components with run-time arguments, in order to implement the factory pattern.

The factory pattern is a common good practice to create objects that mix both dependencies and parametrization. Factories are also very suited to “hide” dependencies of the created objects, which the creator does not have to care about. Factories are usually a lot of boilerplate code, prone to tedious repetition and human error. By defining components that mix static dependencies and run-time arguments, Typhoon can create these factories for you.

Here's a concrete example: Imagine having two view controllers with the following interfaces:

@interface FriendListViewController
- (instancetype)initWithUsersService:(UsersService *)usersService; // dependency
@end

@interface UserDetailsViewController
- (instancetype)initWithPhotoService:(PhotoService *)photoService // dependency
 user:(User *)user;
@end

FriendListViewController shows a list of users and UserDetailsViewController shows the details of the user passed as an argument, including a nice photo of the user retrieved from PhotoService.

Clearly, if we want FriendListViewController to instantiate UserDetailsViewController, then it's lacking a dependency: PhotoService.

To solve this, one way would be to make FriendListViewController accept a PhotoService as a parameter to its initializer, thereby creating an artificial dependency. This is not the best approach as it might cause a "dependency explosion" — the early instantiation of all of PhotoService's dependencies, which could lead to excessive memory consumption, for instance.

The best solution is to let FriendListViewController use a factory that when invoked that returns an instance of UserDetailsViewController for some specific user.

Run-time arguments allow defining a factory on the assembly interface. Here is an example:

- (id)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];
        }];
    }];
}

So having defined the UserDetailsViewController with a runtime argument, we can now inject the assembly on the FriendListViewController as follows:

- (id)friendListController
{
 return [TyphoonDefinition withClass:[FriendListViewController class]
 configuration:^(TyphoonDefinition *definition) {

 [definition injectProperty:@selector(assembly) with:self];
 }];
}

We can obtain a UserDetailsViewController with both the static and runtime dependencies as follows:

User* aUser = friendListController.selectedUser;
UserDetailsViewController* detailsController = [friendListController.assembly
 userDetailsControllerForUser:aUser];
User* aUser = self.selectedUser;