appsquickly / typhoon

Powerful dependency injection for Objective-C ✨✨ (https://PILGRIM.PH is the pure Swift successor to Typhoon!!)✨✨
https://pilgrim.ph
Apache License 2.0
2.7k stars 269 forks source link

Exception with reason: 'Key is already registered.' #554

Open pinchukvd opened 7 years ago

pinchukvd commented 7 years ago

I have two properties in AppAssembly - SearchModuleAssembly and FavoritesModuleAssembly. AppAssembly, SearchModuleAssembly, FavoritesModuleAssembly are initial assemblies. It's throwing an exception on run "reason: 'Key 'router' is already registered.'".

@interface AppAssembly : TyphoonAssembly
@property (strong, nonatomic) SearchModuleAssembly *searchModuleAssembly;
@property (strong, nonatomic) FavoritesModuleAssembly *favoritesModuleAssembly;
@implementation SearchModuleAssembly
-(UIViewController *)view
{
    return [TyphoonDefinition withClass:[SearchVC class] configuration:^(TyphoonDefinition *definition) {
        [definition useInitializer:@selector(initWithClassNib)];
        [definition injectProperty:@selector(viewModel) with:[self viewModel]];
    }];
}

-(id<ViewModelProtocol>)viewModel
{
    return [TyphoonDefinition withClass:[SearchViewModel class] configuration:^(TyphoonDefinition *definition) {
        [definition injectProperty:@selector(router) with:[self router]];
    }];
}

-(id<RouterProtocol>)router
{
    return [TyphoonDefinition withClass:[SearchRouter class] configuration:^(TyphoonDefinition *definition) {
        [definition injectProperty:@selector(viewController) with:[self view]];
    }];
}
@implementation FavoritesModuleAssembly
-(UIViewController *)view
{
    return [TyphoonDefinition withClass:[FavoritesVC class] configuration:^(TyphoonDefinition *definition) {
        [definition useInitializer:@selector(initWithClassNib)];
        [definition injectProperty:@selector(viewModel) with:[self viewModel]];
    }];
}

-(id<ViewModelProtocol>)viewModel
{
    return [TyphoonDefinition withClass:[FavoritesViewModel class] configuration:^(TyphoonDefinition *definition) {
        [definition injectProperty:@selector(router) with:[self router]];
    }];
}

-(id<RouterProtocol>)router
{
    return [TyphoonDefinition withClass:[FavoritesRouter class] configuration:^(TyphoonDefinition *definition) {
        [definition injectProperty:@selector(viewController) with:[self view]];
    }];
}
jasesuperhero commented 7 years ago

I found a solution for this problem. We can change definition.key from configuration. It will look like this:

return [TyphoonDefinition withClass:[FavoritesRouter class] configuration:^(TyphoonDefinition *definition) {
        definition.key = @"FavoritesModuleAssembly_router"; 
        [definition injectProperty:@selector(viewController) with:[self view]];
    }];

The solution seems to be ugly because we’ll have to change the definition.key on all definitions. @etolstoy Is it possible to delimit definitions on a namespaces, using typhoon functionality? In this article, you mentioned that the key is compound and consists of a random string and the name of the method. What has changed?

jasperblues commented 7 years ago

@jasesuperhero You can also consider namespacing your methods:

favoritesRouter SearchRouter

jasesuperhero commented 7 years ago

@jasperblues Yeah, sure. But if we use TyphoonAssemblies for MVVM or VIPER modules creation we’ll have problems with using common protocol or class with the same names in one module. Every VIPER module has view, presenter, interactor, also we have some runtime magic using the exact names. For example:

@protocol MVVMAssebly <NSObject>

@required
- (id)model;
- (id)view;
- (id)viewModel;

@end
jasperblues commented 7 years ago

Relates to: https://github.com/appsquickly/Typhoon/pull/446

jasesuperhero commented 7 years ago

In #446 pr we got the opportunity to create definitions with configs, that has specific definition.key. But in current case it's not useful, because we want to create definitions with uniq keys in range of one assembly and using configuration in code. I found the method -setDefinitionKeyRandomlyIfNeeded, that can resolve current problem, but it will called, if key.length == 0. it's not very comfortable to set empty key in all definitions. Do you have any ideas? Maybe we can create pr with new initializers for definition, that set key to empty.