Swinject: Dependencies of ViewControllers are getting injected multiple times using reference Storyboards

Created on 11 Jul 2016  路  8Comments  路  Source: Swinject/Swinject

Info

Swinject Version: 1.1.2
Xcode Version: Version 7.3.1 (7D1014)

Steps to reproduce

  1. Instantiate Initial RootViewController described in Without SwinjectStoryboard
  2. embed the template ViewController in a UITabbarController
  3. create another storyboard called Second.storyboard
  4. create a new view controller called SecondViewController
  5. make a connection between the UITabbarController in Main.storyboard and the SecondViewController in Second.storyboard
  6. register ViewControllers and dependencies like the following code example
  7. Run the Application

    Example Configuration

@UIApplicationMain
  class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    let container = SwinjectStoryboard.defaultContainer

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.


        // Configure Swinject

        container.registerForStoryboard(CustomTabbarController.self) {
            r, vc in
            print("registerForStoryboard: CustomTabbarController")
            vc.viewModel = r.resolve(TabbarViewModel.self)
        }

        container.register(TabbarViewModel.self) {
            r in
            print("register: TabbarViewModel")
            return TabbarViewModel()
        }

        container.registerForStoryboard(FirstViewController.self) {
            r, vc in
            print("registerForStoryboard: FirstViewController")
            vc.viewModel = r.resolve(FirstViewModel.self)
        }

        container.register(FirstViewModel.self) {
            r in
            print("register: FirstViewModel")
            return FirstViewModel()
        }

        container.registerForStoryboard(SecondViewController.self) {
            r, vc in
            print("registerForStoryboard: SecondViewController")
            vc.viewModel = r.resolve(SecondViewModel.self)
        }

        container.register(SecondViewModel.self) {
            r in
            print("register: SecondViewModel")
            return SecondViewModel()
        }

        setupInitialView()

        return true
    }

    private func setupInitialView() {

        // We need to setup our view und rootViewController manually, so we
        // can use the SwinjectStoryboard for our dependency resolving mechanism

        let window = UIWindow(frame: UIScreen.mainScreen().bounds)
        window.makeKeyAndVisible()
        self.window = window

        let storyboard = SwinjectStoryboard.create(name: "Main", bundle: nil, container: container)
        window.rootViewController = storyboard.instantiateInitialViewController()
    }

}

This will produce following log

registerForStoryboard: SecondViewController
register: SecondViewModel
Init SecondViewModel
registerForStoryboard: CustomTabbarController
register: TabbarViewModel
Init TabBarViewModel
registerForStoryboard: FirstViewController
register: FirstViewModel
Init FirstViewModel
registerForStoryboard: SecondViewController // <---- Second Time
register: SecondViewModel  // <---- Second Time
Init SecondViewModel

As you can see, the factories for SecondViewController and SecondViewModel are called twice.
I think this has something to do with container view controllers and it's child view controllers. As I debugged SwinjectStoryboard if found out, that injectDependency calls injectDependency for all child view controllers, but the injection for the child view controllers of the UITabBarController are already done at this point and will be repeated again.

    private func injectDependency(viewController: UIViewController) {
        let registrationName = viewController.swinjectRegistrationName

        // Xcode 7.1 workaround for Issue #10. This workaround is not necessary with Xcode 7.
        // The actual controller type is distinguished by the dynamic type name in `nameWithActualType`.
        //
        // If a future update of Xcode fixes the problem, replace the resolution with the following code and fix registerForStoryboard too:
        //    container.resolve(controller.dynamicType, argument: controller as Container.Controller, name: registrationName)
        let nameWithActualType = String(reflecting: viewController.dynamicType) + ":" + (registrationName ?? "")
        container.value.resolve(Container.Controller.self, name: nameWithActualType, argument: viewController as Container.Controller)

        // <---- This causes the multiple factory invocation.
        for child in viewController.childViewControllers {
            injectDependency(child)
        }
    }

Storyboard Example

_Main.storyboard_
screen shot 2016-07-11 at 5 13 56 pm

_Second.storyboard_
screen shot 2016-07-11 at 5 14 57 pm

bug help wanted

All 8 comments

@phillippbertram, thank you for submitting the issue馃槂 It looks a bug. I'm not sure it's a bug only in the latest iOS, or in older versions of iOS too, because I think I intentionally added the recursive code in injectDependency.

for child in viewController.childViewControllers {
    injectDependency(child)
}

I need some investigation.

@yoichitgy you're welcome :)
did you already investigate this problem and can tell me, when you can fix this?

Thanks a lot

Hi @phillippbertram.

I still didn't investigate the issue. Hopefully I'll be able to investigate the issue by the release of Swinject v2.

Each Storyboard link creates SwinjectStoryboard instance. If controller1 is located in storyboardA and has child controller2 located in storyboardB, controller2 will be resolved 2 times during recursive injection of controller1 children's dependencies and when instantiateViewControllerWithIdentifier for storyboardB will be called

@VladimirBorodko, thank you for investigating the issue馃槂

This issue is fixed by PR #15 in SwinjectStoryboard repo for Swinject v2. It might be good to port the changes to Swinject v1.

Fixed by @jakubvano in pull request #144. It will be available in v1.1.4 soon.

awesome 馃拑 馃憤

thanks a lot

Was this page helpful?
0 / 5 - 0 ratings

Related issues

oronbz picture oronbz  路  3Comments

diwengrum picture diwengrum  路  5Comments

thalmicMark picture thalmicMark  路  6Comments

irace picture irace  路  5Comments

Mackarous picture Mackarous  路  4Comments