I am researching dependency injection and am currently in the process of updating my project to make use of it. However, I am having issues with associated type and protocol conforming.
I have created a quick demo project and I have created a few protocols and extensions so that viewControllers that conform to my protocol ViewModelBased must implement an associatedtype. Ideally I want this associatedtype to conform to a viewModel. Here is what I have so far
protocol ViewModel {
associatedtype Services
init (withServices services: Services)
}
protocol ViewModelBased: class {
associatedtype ViewModelType
var viewModel: ViewModelType { get set }
}
extension ViewModelBased where Self: UIViewController{
static func instantiateController(with viewModel : ViewModelType) -> Self {
// I have created UIStoryboard extension to allow for easy opening of view controllers
// in storyboard
let viewController : Self = UIStoryboard.mainStoryboard.instantiateViewController()
viewController.viewModel = viewModel
return viewController
}
}
So all viewModels in my app conform to ViewModel which forces them to implement a service type. For example, my LoginModel looks like this
struct LoginModel : ViewModel{
// service type
typealias Services = LoginService
// init service
var services : LoginService
init(withServices services: LoginService) {
self.services = services
}
/// calls login service - attempts login api
func attemptLogin() {
services.login()
}
}
So here is an example of a viewController that implements this
class SecondController: UIViewController, ViewModelBased {
var viewModel: LoginModel!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func loginTest() {
viewModel.services.onLoginSuccess = { isVerified in
print(isVerified)
}
viewModel.services.onLoginFailure = { errorCode in
print(errorCode)
}
viewModel.attemptLogin()
}
}
So putting this together, this allows the app to init a viewController and pass in a viewModel like this
let loginModel = LoginModel(withServices: LoginService())
let controller = SecondController.instantiateController(with: loginModel)
self.navigationController?.pushViewController(controller, animated: true)
This all works very well, but the problem I am having is, the associated Type can be any type at the moment. Ideally I want this associatedType to conform to the ViewModel protocol. However when I try this
protocol ViewModelBased: class {
associatedtype ViewModelType : ViewModel
var viewModel: ViewModelType { get set }
}
My SecondController now throws an error and now forces me to init the LoginModel
var viewModel : LoginModel = LoginModel(withServices: LoginService())
But this is no longer making use of dependency injection as the viewController is now in charge of creating the viewModel instance and knows about the behavior of the viewModel class.
Is there a way I can fix this? Would be very grateful if someone could give me some information on what I am doing wrong.