You may have heard about dependency injection. I’ve written about it before, but I wanted to briefly discuss a simple, effective way of applying this principle to your apps. Essentially, views shouldn’t contain any business logic, view controllers are views, therefore view controllers shouldn’t contain any business logic. View controllers should instead ask a sibling object for the data. This sibling object can be many forms, but the two most effective forms are ViewModel and DataController. With ViewModel, each view controller has a view model class. The view model class does all the heavy lifting and has a reference to the view, abstracted as a protocol. The view model then calls methods on the view via the protocol.
To illustrate this, below is a very simple example.
Now all our database accessing, web service requests, etc, all sit in the view model. The view model then calls functions on the view via the AwesomeView protocol. So now we have a dumb reusable view controller.
The second way of separating concerns like this is through a data controller. A data controller is essentially exactly the same as a view model – don’t worry! The difference being that a the relationship is different. With view model, each view controller has a one to one relationship with a view model. With data controller, a collection of view controllers share a single data controller (this collection of view controllers will be a flow in your application). Since the data controller now can’t have a view property on it, it must instead communicate via closures. Again, here’s a simple example:
In this way, the view controllers calls methods on the data controller and the data controller passes something back for the view to use. This is a little less separated compared to view model, but there’s a lot less boiler plate setup and views can still be pretty serrated if done right.
Ok so that’s a brief of separation of concerns. Now let’s look at how we can use dependency injection to inject these dependencies into our view controllers. You see how in both examples the view controller creates an instance of the view model or data controller? That’s the bad bit. See, now we can’t control what the view controller uses. It will always use that implementation. We can’t inject a mock version for testing, or even for development. For example, say you’re building against an API spec but the API isn’t online yet. You want to be able to inject dependencies into your view controllers. But there’s a problem, because view controllers are (typically) instantiated from storyboards. When this happens, we can’t pass arguments into a view controller’s init method.
The best solution I’ve found is to create a module. The inspiration comes from Dagger 2, a dependency injection framework for Android. Essentially you create a module for each group of things, and when the view controller asks the module for a dependency, the module provides it.
The difference now is that rather than creating an instance of our data controller in our view controller, the view controller instead asks the DataModule struct for it. Now, everything that wants AwesomeDataControllerConformable asks the DataModule for it, meaning we can very easily return a mock implementation if we want.
You can apply the same principle for view models, by creating a ViewModelModule struct and having functions that take the view as an argument and return the view model implementation you want.