Massive view controller / MVVM
In this post I’ll talk about MVVM - Model, View, View Model - pattern as another solution to massive view controller.
MVVM vs MVC
There doesn’t seem to be much of a difference between the two patterns besides different naming scheme. In fact, view model in MVVM takes the job of controller in MVC, right!? Looking from Cocoa/UIKit point of view, it may seem like an additional overhead, thrown into working architecture by bored programmers. We already have controller, so why do we need view model - should we discard controllers or simply name them differently?
However, MVC can greatly benefit by introducing view models. And no, it doesn’t contradict with controllers but instead complements it and helps moving from massive view controller. When using in MVC, you can think of view model as a component sitting between controller and model layer, so we actually get MVCMV (which coincidentally also represents roman number 1900 :)
Ideally view should only be updated from controller, that’s why the two lines are dimmed on the chart. But in practice having direct access to view model is sometimes convenient, for example when model represented by view uses many properties.
Parent and child controllers
Let’s imagine we have a model describing folder: each folder has a name and size:
class FolderData {
lazy var name = ""
lazy var size = 0
}
Then we have a view controller that shows information about specific folder:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let sizeInKB = self.folder.size / 1024
self.nameLabel.text = self.folder.name
self.sizeLabel.text = "\(sizeInKB)"
}
var folder: FolderData!
@IBOutlet var nameLabel: UILabel!
@IBOutlet var sizeLabel: UILabel!
}
Now imagine there’s a second controller which is used to rename the folder:
class FolderRenameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let sizeInKB = self.folder.size / 1024
self.nameTextField.text = self.folder.name
self.sizeLabel.text = "/(sizeInKB)"
}
@IBAction func textFieldEditingDidEnd(sender: AnyObject) {
self.folder.name = self.nameTextField.text
}
var folder: FolderData!
@IBOutlet var nameTextField: UITextField!
@IBOutlet var sizeLabel: UILabel!
}
We’re accessing rename controller from a segue triggered by a button on parent controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "FolderRenameScene" {
let destinationController = segue.destinationViewController as! FolderRenameController
destinationController.folder = self.folder
}
}
All good and well. Except currently parent view controller doesn’t reflect changes to name performed on rename controller. So we should be using some form of callbacks to notify parent view controller of the change.
There are couple methods to deal with this:
- Add delegate on child controller through which we’ll inform parent controller about change. Parent controller would then update its views accordingly. This is typical implementation for passing back data or notifications from child to parent controller.
- Add block on child controller through which we’ll inform parent controller about change. This is simply a variation of delegate pattern. I tend to use blocks (or closures) more than delegates in Swift. They allow in-place responding to changes instead of breaking up controller setup and change handling to separate methods.
- Use
NSNotificationCenter
to notify of change. Posting notification from child controller and handling it on parent which would then update its views. Downside of this approach is there’s no apparent connection between child and parent. Upside is notification can be posted from anywhere in the application, even from objects not directly linked with ours and they will still be handled correctly (this is more relevant in Mac applications). Also notifications allow many observers listening for the same change. - Use KVO to observe change to individual properties of the model. This approach is similar to notifications. It also doesn’t require explicit connection with controller that changes the model (the change can be made anywhere and our controller would still be notified of it). I find KVO less confusing than notifications: there’s explicit connection with object which values we’re observing and object that observes the values. Downside of KVO is horrible API. Also with Swift, you need to explicitly decorate properties which should trigger KVO with
dynamic
and subclassNSObject
. KVO is especially useful on Mac, as we can bind values which eliminates the need to use KVO API.
For the purpose of this post, I’ll use KVO. To keep project simple, I’ll use default API, but for real projects, I’d recommend using one of open source libraries that make KVO bearable. There are several, the one I tend to rely on is THObserversAndBinders - it’s lightweight, simple to understand and easy to use and supports block and target/action based callbacks. It also brings key value binding to iOS. Another popular framework is ReactiveCocoa - it’s very powerful and certainly interesting, but it presents substantial learning step. It also means adding a complex third party code to your app which brings potential future maintenance risk, especially since it’s not trivial to replace it.
I won’t demonstrate KVO code here - it’s conceptually simple but requires lots of boiler plate code. You can check it in example project. The point I’m driving at is there’s a lot of code starting to creep up in our controllers that’s only a distraction to real responsibilities of those classes. Here’s where MVVM comes to rescue!
MVVM comes to rescue
Steps at a glance:
- Create separate class for view model.
- Move notifications/observing from controllers to view model.
- Move convenience properties/methods from controllers to view model.
- Wrap model into its view model and use view model as controller’s “represented object”.
With MVVM we’ll introduce another object - view model - that will handle common data flow on behalf of controllers. This will allow extracting parts of code from controller, thus making it simpler and more focused. In addition it’ll promote code reuse. Let’s dive straight in:
private var NameDidChangeContext = 0
private var SizeDidChangeContext = 0
class FolderViewModel {
init(folder: FolderData) {
self.folder = folder
self.folder.addObserver(self, forKeyPath: "name", options: nil, context: &NameDidChangeContext)
self.folder.addObserver(self, forKeyPath: "size", options: nil, context: &SizeDidChangeContext)
}
deinit {
self.folder.removeObserver(self, forKeyPath: "name")
self.folder.removeObserver(self, forKeyPath: "size")
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if (context == &NameDidChangeContext) {
self.nameDidChange?(self.folder.name)
} else {
self.sizeDidChange?(self.sizeInKB)
}
}
var sizeInKB: Int {
return self.folder.size / 1024
}
private (set) internal var folder: FolderData
var nameDidChange: ((name: String) -> ())?
var sizeDidChange: ((size: Int) -> ())?
}
As you can see there’s not much to it. It implements KVO for folder properties and translates them to blocks. It also implements convenience property for calculating folder size in KB.
Note this implementation only allows single observer for each of the two blocks. This effectively means view models can’t be shared between controllers - each controller will need to create a new instance of view model in order to be able to listen for changes. This should not pose much of a problem; view models tend to be lightweight wrappers over model objects. However if creation should become expensive, the alternative would be using different change propagation mechanism that will allow multiple observers for single property; here’s an example.
Of course another alternative would be to implement observations directly on controllers where needed. In fact, that’s usually how I do it but using THObserver
or THBinder
. However if your project requirements prevent you from using third party libraries, you either need to implement to-many observer class yourself (which, frankly isn’t much of a deal) or rely on builtin KVO API.
For the purpose of this simplified example, I chose to implement KVO in view model to demonstrate such a use.
Now let’s see how using view models translates to our controllers:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.nameLabel.text = self.folderViewModel.folder.name
self.sizeLabel.text = "\(self.folderViewModel.sizeInKB)"
self.folderViewModel.nameDidChange = { name in
self.nameLabel.text = name
}
self.folderViewModel.sizeDidChange = { size in
self.sizeLabel.text = "\(size)"
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "FolderRenameScene" {
let destinationController = segue.destinationViewController as! FolderRenameController
destinationController.folder = self.folder
}
}
var folder: FolderData! {
didSet {
self.folderViewModel = FolderViewModel(folder: newValue)
}
}
private var folderViewModel: FolderViewModel!
@IBOutlet var nameLabel: UILabel!
@IBOutlet var sizeLabel: UILabel!
}
Note how we’re leaving folder
property as it was - assuming that’s how parent controller will pass data to MyViewController
. This leaves public interface unchanged. It also allows view controller to initialize whichever view model class it needs.
There’s similar change to FolderRenameViewController
. Check source code for details - I included two projects - MVVMBefore and MVVMAfter so you can compare the code. Pay attention to how controller code becomes simpler and more readable after introducing view model, even in this simple example!
Results & implications
By introducing view model, we could cleanup glue code from view controllers. At the same time we’re promoting code reuse by implementing commonly used helper properties or methods in view controller instead of all over our code. We’re also better separating responsibilities of components. And last, but not least, the code is more unit testable this way. In general view controllers aren’t the easiest parts of our app to unit test but view models are.
Many times we’re adding convenience properties and methods directly to models. In this case introducing view model may seem like unnecessary complication. Sample app attached to this post doesn’t demonstrate that, but each controller may have its own version of view model. In fact, it’s quite common to do so. Especially if view controller needs different interpretation of model data. Of course that doesn’t mean duplicating all common behavior - we can still have view model base class for dealing with KVO and other common stuff and then controllers that have unique requirements can subclass and implement their specifics (remember “forest of trees” from previous post). Don’t be shy to create view model class hierarchy for single model class; it’s better to have several focused view model classes then to stuff everything into single one and then never know the difference between sizeInKB
, roundedSizeInKB
and sizeInKBForSlider
. View models are tied with view controllers that use them.
Another frequent use of view models is to act as an adapter to underlying models. For example: we want to reuse the same controller for two or more types of models. If corresponding model classes vary, we could create a view model that unifies interface for controller and hence acts as adapter. This typically means there’s base view model class that specifies interface and subclass for each concrete type of model which simply wires model’s properties into unified interface and back.
The drawbacks to view models are more complex class structure and potential creation cost of view models. First one can be managed by keeping related view model and controllers inside same Xcode group (I find myself much more inclined towards creating smaller helper classes in Swift than I am in Objective-C because of using single file vs. header and declaration). Creation cost can be addressed by reusing view model instances whenever possible. That may mean passing the same view model instance around or, if view model classes are not the same, creating view model once and then assigning different models as they are passed to controller.
Note that some developers promote 1:1 pass-through properties or methods from view model to model. There’s a point to that, but I find it too cumbersome to use and maintain in practice, especially for models with lots of properties. Instead, I tend to allow access to underlying model.
Pros:
- More manageable view controller code.
- Clearer separation of responsibilities.
- Focused convenience code managing.
- Simpler convenience code reuse across controllers.
- Simpler to unit test.
Cons:
- More complex class structure.
- Potential creation cost for view models.
When to use:
- View controllers contains lots of boiler plate model handling code.
- Loss of code focus, lots of (duplicated) convenience methods or properties.
- Reuse boiler place model code between multiple controllers.
- Adapt variable model interface to unified interface.
Conclusion
View models provide another relief for overcrowded view controllers. Lots of boilerplate code can be extracted which gives controllers more focus. It also allows sharing helper methods and properties between controllers without relying on (and frequently overusing) bolted on code in model layer. It’s a win-win: it not only cleans up controllers but also model objects. At the same time, writing unit tests for view models is very simple, which is yet another benefit.
This is second post in series related to MVC pattern. Here’s previous post where I write about dealing with data sources. Also see readme file on github which serves as a cross reference to all MVC related posts. There are more ideas for future articles. In the meantime - if you have questions, have concrete topics or examples you’d like me to go over, feel free to contact me using links at the bottom!
Download sample code from GitHub.