When using MVVM in a storyboard driven app, you’ve most likely run into issues safely injecting view models into view controllers. Since view controllers are automatically initialized by storyboards, view models cannot be injected into view controllers at creation time. So we need to investigate other solutions. We’ll never get compile time type safety, but we can assert our view model at runtime and give the compiler a hint about the type of our view model.
Natasha The Robot uses a protocol based approach which allows the injection of all the data needed by the view controller. However, it requires quite a bit of boilerplate in the view controller. I took a different approach using an abstract/base class, at the expense of making the view model public. Too bad Swift doesn’t provide protected access control. It would have enabled us to privately encapsulate our view model inside our view controller without access to the rest of the world.
Although I generally try to avoid subclassing, this is a clear case where it’s useful. It allows us to not have any view model related boilerplate in our view controllers. We define a generic view controller that contains a view model. Since we don’t have the ability to check the view model during creation time, we can do the next best thing, assert it when the view is loaded from the storyboard.
Now we can specialize our view controller subclasses with their associated view models. Let’s create a custom view controller that is specialized with a simple view model struct named
CustomViewModel that contains some initial data.
Now the view controller is explicitly bound to it’s view model. When the view model is assigned to the view controller, the compiler knows its specific type. We get things like Xcode auto-complete, for free.
The view model is typically injected in
prepare(for segue:sender:) if using storyboards with segues, but can be anywhere where the view controller is first setup. In some architectures, view controllers are setup in view controller factories, or view coordinators/presenters. Note that whatever approach is taken, the view model is assumed to have some kind of initial state. It’s nice to have view models that are completely independent of their view controllers. This way, they’re more testable and reusable.
The last step in getting this approach to work is a bit hacky, but is needed to get storyboards to play nice with generics. In the end it’ll leave you with a much cleaner MVVM architecture. The key is to reference each view controller before
UIApplicationMain is run. This makes each class available to the Objective-C runtime. We can do this by simply printing each controller.
AppDelegate. Then add the following
The workaround for a bug on line #9 is beyond the scope of this post, but now you have a solid mechanism for injecting view models into storyboard created view controllers.
Another approach to this problem is to use method swizzling and add the view model assertion check during the view lifecycle. The benefit of that is we wouldn’t have to subclass
UIViewController anymore. However, I wanted to avoid swizzling for now. If you go with that approach, make sure to only do it for debug builds.
Automate the process of generating
main.swift using Sourcery, a Swift meta-programming tool.