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()
}