Search code examples
iostyphoon

Circular dependency when pushing view controllers


I have seen that the scope TyphoonScopeObjectGraph is useful for having circular dependencies, such a a controller and view, that has a delegate property pointing back to the controller.

So I tested this with the following scenario:

Controller A pushes Controller B which pushes Controller C that has a weak delegate pointing back to Controller A.

Controller C has a button that executes a method on this delegate.

I have seen that Typhoon instantiates Controller C, it properly sets Controller A as delegate, however when the button is pressed the delegate has been updated to nil.

This is the assembly code:

- (ViewControllerA *)viewControllerA {
    return [TyphoonDefinition withClass:[ViewControllerA class]
                          configuration:^(TyphoonDefinition *definition) {
        [definition injectProperty:@selector(name) with:@"Hello world!"];
    }];
}

- (ViewControllerC *)viewControllerC {
    return [TyphoonDefinition withClass:[ViewControllerC class]
                          configuration:^(TyphoonDefinition *definition) {
        [definition injectProperty:@selector(delegate) with:[self viewControllerA]];
    }];
}

This is ViewControllerC:

@interface ViewControllerC : UIViewController

@property (weak, nonatomic) id<ViewControllerDelegate> delegate;

@end

@implementation ViewControllerC

- (IBAction)buttonAction:(id)sender {
    [self.delegate viewControllerDidTapButton:self];
}

@end

This is ViewControllerA:

@interface ViewControllerA ()<ViewControllerDelegate>

@end

@implementation ViewControllerA

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = self.name;
}

- (void)viewControllerDidTapButton:(UIViewController *)viewController {
    [self.navigationController popToRootViewControllerAnimated:YES];
}

Why is this? Is there a workaround?


Solution

  • The TyphoonScopeObjectGraph creates a shared scope only during a call to assemble a particular definition. So if:

    • View Controller A is built in one call.
    • And then View Controller C is built, creating a dependency on View Controller A will create a new instance, which because of the weak reference, will subsequently go nil again.

    If you want to create a circular dependency back to something that was built earlier you need either TyphoonScopeSingleton or TyphoonScopeWeakSingleton. The latter sounds appropriate in this case - for our own projects we sometimes use that scope for a shared 'context' object that should be discarded when no more references point to it.