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 270 forks source link

Circular reference level depth #597

Closed sdkdimon closed 5 years ago

sdkdimon commented 5 years ago

Start from version 4.0.5 circular reference level of depth can't be grater than one. But unlimited circular reference level of depth was one of my favorite feature. Is there some reasons to cut level of depth? Maybe implement some property to control level of depth.

alexgarbarev commented 5 years ago

There is shouldn’t be an issue with it, and there is unit tests for that. Share your usage example and provide more detail (or ideally write a failing test)

sdkdimon commented 5 years ago

Here sample of code, assembly of Login V.I.P.E.R module. The problem is that Typhoon create new instance of LoginViewController for JRViewControllerRoute owner property. I think that in terms of ObjectGraph scope LoginViewController must be the same instance for LoginPresenter and JRViewControllerRoute, in Typhoon v4.0.4 it works, but in v4.0.5 was broken, by limiting injection level of depth.

- (LoginViewController *)loginView
{
    return [TyphoonDefinition withClass:[LoginViewController class] 
                          configuration:^(TyphoonDefinition *definition) {
                [definition injectProperty:@selector(output)
                                      with:[self presenterLogin]];
            }];
}

- (LoginPresenter *)presenterLogin
{
    return [TyphoonDefinition withClass:[LoginPresenter class]
                          configuration:^(TyphoonDefinition *definition) {
                              [definition injectProperty:@selector(view)
                                                    with:[self loginView]];
                              [definition injectProperty:@selector(interactor)
                                                    with:[self interactorLogin]];
                              [definition injectProperty:@selector(router) 
                                                    with:[self routerLogin]];
            }];
}

- (LoginInteractor *)interactorLogin
 {
    return [TyphoonDefinition withClass:[LoginInteractor class]
                          configuration:^(TyphoonDefinition *definition) {
                              [definition injectProperty:@selector(output)
                                                    with:[self presenterLogin]];
            }];
}

- (LoginRouter *)routerLogin
{
    return [TyphoonDefinition withClass:[LoginRouter class]
                          configuration:^(TyphoonDefinition *definition) {
                              [definition injectProperty:@selector(routeRegistration)
                                                    with:[self routeRegistration]];
            }];
}

- (JRViewControllerRoute *)routeRegistration
{
    return [TyphoonDefinition withClass:[JRViewControllerRoute class] 
                          configuration:^(TyphoonDefinition *definition) {
            [definition injectProperty:@selector(owner)
                                  with:[self loginView]];
            [definition injectProperty:@selector(destinationFactoryBlock)
                                  with:^UIViewController * {
                                    return [self.registrationAssembly viewRegistration];
                                    }];
            }];
}
alexgarbarev commented 5 years ago

Ok, I'll take a look. How do you build the instance? What is the first instance to build? Do you build LoginViewController manually or via Storyboard?

alexgarbarev commented 5 years ago

I see the only change in v4.0.5 is fixing circular reference bug for prototype instances. I'll take a look..

sdkdimon commented 5 years ago

First instance that builds is LoginViewController, builded manualy not via Storyboard. Some other module injects block, which returns LoginViewController by calling loginView from LoginAssembly into own JRViewControllerRoute and module starts build, when logic of app call method on JRViewControllerRoute instance. Here samle:

- (JRViewControllerRoute *)routeToLogin
{
    return [TyphoonDefinition withClass:[JRViewControllerRoute class] 
                          configuration:^(TyphoonDefinition *definition) {
            [definition injectProperty:@selector(owner)
                                  with:[self ownerView]];
            [definition injectProperty:@selector(destinationFactoryBlock)
                                  with:^UIViewController * {
                                    return [self.loginAssembly loginView];
                                    }];
            }];
}

Header in LoginAssembly module assembly have one method.

- (LoginViewController *)loginView;
alexgarbarev commented 5 years ago

Hey @sdkdimon,

I've tried to replicate the issue you described and couldn't. All worked as expected to me. There is new commit in master with unit test to fail, you can modify it to get it failing. What I did: 1) Assembly:

- (LoginViewController *)loginView
{
    return [TyphoonDefinition withClass:[LoginViewController class]
                          configuration:^(TyphoonDefinition *definition) {
                              [definition injectProperty:@selector(output)
                                                    with:[self presenterLogin]];
                          }];
}

- (LoginPresenter *)presenterLogin
{
    return [TyphoonDefinition withClass:[LoginPresenter class]
                          configuration:^(TyphoonDefinition *definition) {
                              [definition injectProperty:@selector(view)
                                                    with:[self loginView]];
                              [definition injectProperty:@selector(interactor)
                                                    with:[self interactorLogin]];
                              [definition injectProperty:@selector(router)
                                                    with:[self routerLogin]];
                          }];
}

- (LoginInteractor *)interactorLogin
{
    return [TyphoonDefinition withClass:[LoginInteractor class]
                          configuration:^(TyphoonDefinition *definition) {
                              [definition injectProperty:@selector(output)
                                                    with:[self presenterLogin]];
                          }];
}

- (LoginRouter *)routerLogin
{
    return [TyphoonDefinition withClass:[LoginRouter class]
                          configuration:^(TyphoonDefinition *definition) {
                              [definition injectProperty:@selector(routeRegistration)
                                                    with:[self routeRegistration]];
                          }];
}

- (JRViewControllerRoute *)routeRegistration
{
    return [TyphoonDefinition withClass:[JRViewControllerRoute class]
                          configuration:^(TyphoonDefinition *definition) {
                              [definition injectProperty:@selector(owner)
                                                    with:[self loginView]];
                              [definition injectProperty:@selector(destinationFactoryBlock)
                                                    with:^UIViewController * {
                                                        return [UIViewController new];
                                                    }];
                          }];
}

2) Models:

@interface LoginViewController : NSObject
@property (nonatomic, strong) id output;
@end

@interface LoginPresenter : NSObject
@property (nonatomic, weak) id view;
@property (nonatomic, strong) id interactor;
@property (nonatomic, strong) id router;
@end

@interface LoginInteractor : NSObject
@property (nonatomic, weak) id output;
@end

@interface LoginRouter : NSObject
@property (nonatomic, strong) id routeRegistration;

@end

@interface JRViewControllerRoute : NSObject
@property (nonatomic, weak) id owner;
@property (nonatomic, copy) UIViewController*(^destinationFactoryBlock)();
@end

3) Test:

- (void)test_viper_example_circular_reference
{
    CircularDependenciesAssembly *activatedAssembly = [[CircularDependenciesAssembly assembly] activated];

    LoginViewController *viewController = [activatedAssembly loginView];
    XCTAssertNotNil(viewController);

    LoginPresenter *presenter = viewController.output;
    LoginRouter *router = presenter.router;
    JRViewControllerRoute *route = router.routeRegistration;

    XCTAssert(route.owner == viewController, @"owner should be same viewController");

}

Please check my test and say if something mismatch your example.

sdkdimon commented 5 years ago

Thanks for the tests, I played with them and the described case works without problems. I tryed different configuration cases in test and tests not failing. Seems to be something wrong in my project. So, I close the problem and roll back the typhoon to version 4.0.4, in which the injections work as expected.

hardworker commented 5 years ago

I have the same problem in very similar workflow but controller is builded via storyboard which produces prototype elements. So 4.0.5 fix breaks my workflow.