iOS: Swinject correct way to create hierarchy of dependency injection containers

1.1k Views Asked by At

I have used sample project from KooberApp (it is Raywenderlich book advanced architectures example project) and tried to replace custom Inversion of Control Containers and Dependency Injection code with usage of some framework. I think most popular Dependency Injection framework for iOS is Swinject. There I can register components for services. I would like to resemble original app components lifespan. After some run and try, application seems to work correctly. But I am not 100% sure that approach I have used is the best, and that I haven't missed something important. I think I can still have some inconsistency with components lifespan i.e. usage of scopes .container, .graph, .transient, .weak

I someone could advise whether this Container implementation is correct or something should be fixed, changed, modified, do better?

App Container

import Swinject
import SwinjectAutoregistration

public class DIAppContainer {

    public class func get() -> Container {

        let container = Container()

        container.register(Container.self, name: "main") { r in
            return DIMainContainer.get(parent: container)
        }

        container.autoregister(UserSessionCoding.self, initializer: UserSessionPropertyListCoder.init)
        container.autoregister(AuthRemoteAPI.self, initializer: FakeAuthRemoteAPI.init)

        #if USER_SESSION_DATASTORE_FILEBASED
        container.autoregister(UserSessionDataStore.self, initializer: FileUserSessionDataStore.init)
        #else
        container.autoregister(UserSessionDataStore.self, initializer: KeychainUserSessionDataStore.init)
        #endif
        container.autoregister(UserSessionRepository.self, initializer: KooberUserSessionRepository.init)
        container.autoregister(MainViewModel.self, initializer: MainViewModel.init).inObjectScope(.container)

        container.register(LaunchViewModel.self) { r in
            return LaunchViewModel(userSessionRepository: r.resolve(UserSessionRepository.self)!, notSignedInResponder: r.resolve(MainViewModel.self)!, signedInResponder: r.resolve(MainViewModel.self)!)
        }.inObjectScope(.transient)

        container.register(LaunchViewController.self) { r in
            let vc = LaunchViewController(viewModel: r.resolve(LaunchViewModel.self)!)
            return vc
        }

        container.register(MainViewController.self) { r in
            let vc = MainViewController( viewModel: r.resolve(MainViewModel.self)!,
                                        launchViewController: r.resolve(LaunchViewController.self)!,
                                        mainContainer: r.resolve(Container.self, name: "main")! )
            return vc
        }

        return container
    }
}

Main Container

public class DIMainContainer {

    public class func get(parent: Container) -> Container {

        let container = Container(parent: parent, defaultObjectScope: .container)

        container.register(Container.self, name: "onboarding") { r in
            return DIOnboardingContainer.get(parent: container)
        }
        container.register(Container.self, name: "signedin") { r in
            return DISignedInContainer.get(parent: container)
        }

        container.autoregister(OnboardingViewModel.self, initializer: OnboardingViewModel.init).inObjectScope(.weak)

        container.register(OnboardingViewController.self) { r in
            return OnboardingViewController(viewModel: r.resolve(OnboardingViewModel.self)!, onboardingContainer: r.resolve(Container.self, name: "onboarding")! )
        }.inObjectScope(.transient)

        container.autoregister(SignedInViewModel.self, initializer: SignedInViewModel.init).inObjectScope(.weak)
        container.register(SignedInViewController.self) { (r : Resolver, userSession : UserSession) in
            return SignedInViewController(viewModel: r.resolve(SignedInViewModel.self)!, userSession: userSession, signedinContainer: r.resolve(Container.self, name: "signedin")!)
        }.inObjectScope(.transient)

        return container
    }
}

Signed In Container

public class DISignedInContainer {

    public class func get(parent: Container) -> Container {

        let container = Container(parent: parent)

        container.register(Container.self, name: "pickmeup") { r in
            return DIPickMeUpContainer.get(parent: container)
        }

        //container.autoregister(SignedInViewModel.self, initializer: SignedInViewModel.init)
        container.autoregister(ImageCache.self, initializer: InBundleImageCache.init)
        container.autoregister(Locator.self, initializer: FakeLocator.init)

        // Getting Users Location
        container.register(DeterminedPickUpLocationResponder.self) { r in
            return r.resolve(SignedInViewModel.self)!
        }
        container.register(GettingUsersLocationViewModel.self) { r in
            return GettingUsersLocationViewModel(determinedPickUpLocationResponder: r.resolve(DeterminedPickUpLocationResponder.self)!, locator: r.resolve(Locator.self)!)
        }
        container.register(GettingUsersLocationViewController.self) { r in
            return GettingUsersLocationViewController(viewModel: r.resolve(GettingUsersLocationViewModel.self)!)
        }

        // Pick Me Up
        container.register(PickMeUpViewController.self) { (r: Resolver, location: Location) in

            return PickMeUpViewController(location: location, pickMeUpContainer: r.resolve(Container.self, name: "pickmeup")! )
        }

        // Waiting For Pickup
        container.register(WaitingForPickupViewModel.self) { r in
            return WaitingForPickupViewModel(goToNewRideNavigator: r.resolve(SignedInViewModel.self)!)
        }
        container.register(WaitingForPickupViewController.self) { r in

            return WaitingForPickupViewController(viewModel: r.resolve(WaitingForPickupViewModel.self)!)
        }

        // Profile
        container.register(NotSignedInResponder.self) { r in
            return r.resolve(MainViewModel.self)!
        }
        container.register(DoneWithProfileResponder.self) { r in
            return r.resolve(SignedInViewModel.self)!
        }

        container.register(ProfileViewModel.self) {  (r: Resolver, userSession: UserSession) in
            return ProfileViewModel(userSession: userSession, notSignedInResponder: r.resolve(NotSignedInResponder.self)!, doneWithProfileResponder: r.resolve(DoneWithProfileResponder.self)!, userSessionRepository: r.resolve(UserSessionRepository.self)!)
        }
        container.register(ProfileContentViewController.self) { (r: Resolver, userSession: UserSession) in
            return ProfileContentViewController(viewModel: r.resolve(ProfileViewModel.self, argument: userSession)!)
        }
        container.register(ProfileViewController.self) { (r: Resolver, userSession: UserSession) in
            return ProfileViewController(contentViewController: r.resolve(ProfileContentViewController.self, argument: userSession)!)
        }

        return container
    }
}

Onboarding Container

public class DIOnboardingContainer {

    public class func get(parent: Container) -> Container {

        let container = Container(parent: parent)

        container.register(GoToSignUpNavigator.self) { r in
            return r.resolve(OnboardingViewModel.self)!
        }
        container.register(GoToSignInNavigator.self) { r in
            return r.resolve(OnboardingViewModel.self)!
        }

        container.autoregister(WelcomeViewModel.self, initializer: WelcomeViewModel.init).inObjectScope(.transient)
        container.autoregister(WelcomeViewController.self, initializer: WelcomeViewController.init).inObjectScope(.weak)

        container.register(SignedInResponder.self) { r in
            return r.resolve(MainViewModel.self)!
        }

        container.register(SignInViewModel.self) { r in
            return SignInViewModel(userSessionRepository: r.resolve(UserSessionRepository.self)!, signedInResponder: r.resolve(SignedInResponder.self)!)
        }.inObjectScope(.transient)
        container.autoregister(SignInViewController.self, initializer: SignInViewController.init).inObjectScope(.transient)

        container.register(SignUpViewModel.self) { r in
            return SignUpViewModel(userSessionRepository: r.resolve(UserSessionRepository.self)!, signedInResponder: r.resolve(SignedInResponder.self)!)
        }.inObjectScope(.transient)
        container.autoregister(SignUpViewController.self, initializer: SignUpViewController.init)

        return container 
    }
}

Here App Container is created in AppDelegate, child containers are registered in parent container and init injected to view controllers and then stored in properties and used to initialize this child view controllers

let container : Container = DIAppContainer.get()

Here is MainViewController example with injected child container (Main-Scoped)

  // MARK: - Main-Scoped Container
  let mainContainer: Container

  // MARK: - Properties
  // View Model
  let viewModel: MainViewModel

  // Child View Controllers
  let launchViewController: LaunchViewController
  var signedInViewController: SignedInViewController?
  var onboardingViewController: OnboardingViewController?

  // State
  let disposeBag = DisposeBag()

    // MARK: - Methods
    public init(viewModel: MainViewModel,
               launchViewController: LaunchViewController,
               mainContainer: Container) {
        self.viewModel = viewModel
        self.launchViewController = launchViewController
        self.mainContainer = mainContainer
        super.init()
    }
0

There are 0 best solutions below