mercredi 25 avril 2018

Dependency injection issues with protocols

I have been researching various Dependency injection techniques as would like to introduce more of it into my app. I recently came across this article here

http://www.danielhall.io/a-swift-y-approach-to-dependency-injection

I quite like this approach as rather than doing my current approach of injecting viewModel and services into each new controller, it can be handled by a protocol. So I have setup a protocol like this

So first, I have created some viewModel protocols that looks like this

protocol ViewModel {
    associatedtype Service
    init (withService service: Service)
}

protocol ViewModelBased: class {
    associatedtype ViewModelType
    var viewModel: ViewModelType { get set }
}

So all viewModels that conform to viewModel have to have a service injected into it and it is up to the model to dictate which service is used. All classes that conform to ViewModelBased must contain a viewModel.

I have also created some more generics and protocols to allow controllers to be instantiated with a viewModel quite easily like this

   let loginModel = LoginModel(withService: LoginService())
   let controller = LoginController.instantiateController(with: loginModel)

This works fine but is rather cumbersome as every class that contains a viewModel requires this each time. So by researching the article Daniel hall created, I have come up with this

struct InjectionLoginModelMap {
    static var loginModel = LoginModel(withService: LoginService())
}
protocol LoginModelInjected { }
extension LoginModelInjected {
    var loginModel : LoginModel { get { return InjectionLoginModelMap.loginModel}}
}

I create one of these for each viewModel in the app. So instead of having to instantiate the viewModel myself each time, I just make sure my class conforms to the correct protocol each time. E.g.

class LoginController: UIViewController, LoginModelInjected {

This makes it easy to add multiple dependencies to a controller. It also means I no longer have to inject viewModel into controllers that need it as it is now handled by the protocol. So can initialise controller like this now

let controller = LoginController.instantiateController()

Then when I want to mock the services in unit testing, I can just call this

let loginService = MockLoginService()
let loginModel = LoginModel(withService: loginService)
InjectionLoginModelMap.loginModel = loginModel

This works quite well and is much less cumbersome than the previous method. My Concern however is that these models, once initialised will always be in memory. So even after I have finished with a controller and all its objects have been cleared from memory, the viewModel will remain even though it is no longer needed.

Was hoping for some advice on the best way to deal with this. I was considering maybe changing the InjectionLoginModelMap struct so viewModel is optional like so

struct InjectionLoginModelMap {
    static var loginModel : LoginModel? = LoginModel(withService: LoginService())
}
protocol LoginModelInjected { }
extension LoginModelInjected {
    var loginModel : LoginModel { get { return InjectionLoginModelMap.loginModel!}}
}

Then when I know I have finished with viewController and know that I no longer need access to that viewModel, I can just nillify it like so

InjectionLoginModelMap.loginModel = nil

However this too feels quite cumbersome and ugly. Would be very grateful if someone could give me some advice on best way to solve this. Also any other tips or advice on how others handle dependency injection would be much appreciated.

Thanks!

Aucun commentaire:

Enregistrer un commentaire