简体   繁体   中英

Typhoon: injecting a view controller provider

When using a hand made container/injector responsible for creating and managing the lifecycle of the objects in my graph I use the following pattern(I read in an article about DIY DI in ObjC):

Let's say we have a root view controller that wants to present another one(let's call it detail view controller) passing some data to it. What I normally do is to inject a provider block that takes the data as an argument and returns a new view controller to present. Here is some code...

@implementation RootViewController
{
    UIViewController (^detailViewControllerProvider)(SomeData *someData);
}

- (id) initWithDetailViewControllerProvider:(UIViewController (^)(SomeData *someData))detailViewControllerProvider
{
   //...
   _detailViewControllerProvider = detailViewControllerProvider;
   //...
}

// ...

- (void) presentDetailViewControllerWithData:(SomeData *)someData
{
    UIViewController *detailViewController = _detailViewControllerProvider(someData);
    [self presentViewController:detailViewController animated:YES completion:nil];
}

@end

I could use a factory class as well but this way I avoid creating a new class just creating the provider block in my container class.

The problem comes when I want to use Typhoon as a DI container. How can I use the same pattern? In my detail view controller I am injecting other things and I want to keep using constructor injection, and prefer if it is created by the component factory, not by me. I could create a factory class to create the detail VC, injecting in it(the factory) the dependencies I want to inject later in my detail VC, but it seems too much work comparing to the block based solution.

I don't want to access my component factory from the root view controller and neither use setter injection for someData.

Is there any solution for this?

I would go with a factory.
It seems like a little overhead, but if you do TDD, it will drastically simplify your life as your can easily mock a factory and test your controller in isolation.
It will also work well with a typhoon.

To inject the TyphoonComponentFactory:

  • You can override the lifecycle method - (void)typhoonSetFactory:(id)theFactory in your class.
  • In you assembly interface you can inject a property or argument with self

In any either case, any of your TyphoonAssembly interfaces can pose in front of the factory. So for example if you have an assembly called NetworkAssembly , you can define a property of type NetworkAssembly and inject the factory as that. The documentation for this feature is here .

You can use this approach to roll your own factory that:

  • Pulls the static dependencies from the container.
  • Instantiates the requirement component by injecting the static dependencies along with run-time arguments form your factory interface.

Typhoon Factories w/ Runtime Arguments:

Your TyphoonAssembly interfaces can now implement the above pattern:

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

And now resolve the component:

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

The documentation for this feature is here .

Your assembly interfaces can serve as factories - providing static dependencies along with run-time arguments. The documentation for this feature is here .

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

And now resolve the component:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM