Search code examples
objective-cdependency-injectiontyphoon

Typhoon injection with Run-time Arguments or Factory Providers


I'm a little confused between these two capabilities. Can the same functionality be achieved by either approach. Why create a protocol and allow typhoon to auto generate the implementation if I can just call an assembly with my run time arguments and have that spit out the fully injected object graph?

It seems to me that the assembly with run-time arguments should be the preferred approach. With this I won't have to create large, verbose constructors if I have multiple dependencies that need injected.

For example I can define a factory like this:

@protocol AWDailyDetailsPagingViewControllerFactory <NSObject>

@property (nonatomic, strong, readonly) AWStripViewController *stripViewController;
@property (nonatomic, strong, readonly) AWDailyDetailsDataSource *dailyDetailsDataSource;
@property (nonatomic, strong, readonly) AWLocationListViewController *locationListViewController;
@property (nonatomic, strong, readonly) id<AWPresentationController> presentationController;
@property (nonatomic, strong, readonly) AWPullToRefreshGestureHandler *pullToRefreshGestureHandler;

- (AWDailyDetailsPagingViewController *)dailyDetailsPagingViewControllerWithUserLocation:(AWUserLocation *)userLocation initialLayoutModel:(AWDailyDetailsViewControllerLayoutModel *)initialLayoutModel;

@end

Then I would be required to create a constructor in my ViewController like this:

- (instancetype)initWithStripViewController:(AWStripViewController *)stripViewController
                     dailyDetailsDataSource:(AWDailyDetailsDataSource *)dailyDetailsDataSource
                 locationListViewController:(AWLocationListViewController *)locationListViewController
                     presentationController:(id<AWPresentationController>)presentationController
                pullToRefreshGestureHandler:(AWPullToRefreshGestureHandler *)pullToRefreshGestureHandler
                               userLocation:(AWUserLocation *)userLocation
                         initialLayoutModel:(AWDailyDetailsViewControllerLayoutModel *)initialLayoutModel;

Using this factory protocol is super clean and elegant, so there's no problem there:

[self.dailyDetailsPagingViewControllerFactory dailyDetailsPagingViewControllerWithUserLocation:userLocation initialLayoutModel:initialLayoutModel];

But boy that constructor is just a bit ugly. It seems to me with run-time arguments and assemblies I can avoid that constructor by just injecting into the properties (which I have defined anyway). For example, I believe my assembly would look something like this:

- (id)dailyDetailsPagingViewControllerWithUserLocation:(AWUserLocation *)userLocation initialLayoutModel:(AWDailyDetailsViewControllerLayoutModel *)layoutModel {
    return [TyphoonDefinition withClass:[AWDailyDetailsPagingViewController class] configuration:^(TyphoonDefinition* definition) {
        [definition injectProperty:@selector(stripViewController) with:[self horizontalStripViewController]];
        [definition injectProperty:@selector(dailyDetailsDataSource) with:[self dailyDetailsDataSource]];
        [definition injectProperty:@selector(locationListViewController) with:[self.navigationAssembly locationListViewController]];
        [definition injectProperty:@selector(pullToRefreshGestureHandler) with:[self.navigationAssembly pullToRefreshGestureHandler]];
        [definition injectProperty:@selector(presentationController) with:[self.navigationAssembly presentationController]];
        [definition injectProperty:@selector(userLocation) with:userLocation];
        [definition injectProperty:@selector(layoutModel) with:layoutModel];
    }];
}

Everything is now contained inside the assembly and at runtime I should see the same result as that of the Factory Provider.

So am I on the right track here? Do these two features provide the same functionality? Which one should be used in most cases? Does it matter?

Thanks for any comments and answers!


Solution

  • These two features are essentially compatible.

    We recommend using the newer run-time arguments for the following reasons:

    • Easier to configure.
    • Can propagate arguments down into dependencies
    • Works with circular dependencies
    • No protocol required, just assembly interface. Though you can create one if you wish.

    As it happens, I've created a ticket today, to debate if we can consolidate the two features, and provide just a single solution: