AliSoftware/Reusable

How do you inject dependencies into your view controllers

AndrewSB opened this issue · 5 comments

Hey Oliver, I ran into an issue designing my API and wanted some advice. With many of my view controllers, I require dependencies for the controller to function properly. If I use the instantiate function vended by your library, I create another function that will fill out a bunch of implicitly wrapped optionals, like so:

class MyVC: UIViewController {
    private var userService: UserService!

    static func instantiate(with userService: UserService) -> Self {
         let vc = Self.instantiate() // from Reusable
         vc.userService = userService
         return vc
    }
}

have you come up with a better pattern? Or are you doing something similar

Hi Andrew

In fact that's exactly how I do this too so far.
I haven't found a better way as the library can't guess in advance what properties you'll likely want to inject.

Now, thinking about it, I think there could be an alternative, for example creating a protocol with associated type like this:

protocol InjectionReusable: Reusable {
  associatedtype Dependencies
  var dependencies: Dependencies { get set }
  init(dependencies: Dependencies)
}

extension InjectionReusable {
  init(dependencies: Dependencies) {
    self.dependencies = dependencies
  }
}
extension InjectionReusable where Self: UIViewController {
  static func instantiate(dependencies: Dependencies) -> Self {
    
  }
}

And then you could do:

class MyVC: UIViewController, InjectionReusable {
  typealias Dependencies = UserService & WebService & PersistentManagerService
}

let vc = MyVC.instantiate(dependencies: myDeps)

If you have multiple dependencies you'd need to group them in a struct conforming to the 3 XXXService protocols but that's a practice that is common if you follow Krzysztof's suggestion in http://merowing.info/2017/04/using-protocol-compositon-for-dependency-injection/

Only thing I don't like in this solution is that the dependencies property has to be declared in the protocol, so made public even though it would be more logical to make it private…

Thats a pretty good way to do it, then if you also made the .instantiate function private to the class, they only way to create the object would be through the InjectionReusable. instantiate(dependencies:)

I'm probably going to use this, would you like me to make a PR targeting a new branch on your repo?

The problem is that I'm not sure you can make .instantiate() private, as it's declared and provided by the protocol and its default implementation, and protocols can't declare things private.

That's the exact same reason why we can't make var dependencies: Dependencies { get set } private or at least read-only, because in the protocol's default implementation we need to set its value so the protocol has to declare it publicly visible and settable…

I'd love to have a PR to have InjectionReusable, but I'm curious if it's really doable in a way that doesn't exposes too much private stuff publicly…

Ahh, you're right. It isn't really possible to hide the implementation of Reusable. I can still submit the PR if you'd like, but the benefit seems iffy at best.
Comment here if you'd like me to add it, closing for now though

Yeah at that point I think it's better to let people do their own static func instantiate (deps: Dependencies) on their own class, calling instantiate() + injecting the bars in there, than making that into Reusable but opening too much properties as public in the pod.