harlan kellaway's blog // Themes in Modern iOS Architectures

Published 06-10-2016

Introduction

Cross-posted from the Prolific Interactive blog

It seems I can't go to an iOS meetup or conference without hearing that architectural epithet, "Massive View Controller". It's an easy joke to make with long-suffering implementers of the MVC pattern. Massive View Controller is a play on the acronym MVC, or Model View Controller - the recommended architectural pattern prescribed by Apple. Massive represents the product of attempting to follow traditional MVC, but winding up with all code that is clearly not a Model or a View dumped into View Controllers. This would be code that does any number of things - making network requests, translating models into UI attributes, interacting with persistent storage, and more.

What's great is that the community has become increasingly aware that MVC doesn't cut it in a lot of cases, especially as applications scale. As a result, more and more architectural patterns have been springing up and gaining momentum over the last 3 years. I'm talking MVVM, I'm talking VIPER. I'm talking even newer arrivals at the party, ReSwift and Ziggurat.

Thankfully, resources to explore how to implement these patterns have sprung up increasingly as well. Instead of giving another tutorial, as many pieces do so well, I'd like to explore some of the themes these patterns broach repeatedly. The point of understanding these themes, is that we can make strides towards improving existing code - be it Massive - even if we don't adopt these patterns in their entirety.

Break Up Massive View Controller

This one goes almost without saying. Each architectural pattern has been made to break up the duties that would generally fall within a Massive View Controller. We can see a reflection of this in their key actors:

What's important to notice about these actors is that their responsibilities are clearly defined.

Another way to think about this is through the lens of the Single Responsibility Principle. This principle states that software entities should have one-and-only-one responsibility. A downfall of the MVC architecture is that the Controller's responsibility is too loosely defined - which, in practice, leads to it having (way) more than one responsibility.

Create Dumber View Controllers

Views should be dumb. This means that Views should not have logic that pertains to anything but UI.

Think of View Controllers as the View

I think this theme is visually summed up best by the typical MVVM diagram that shows the View and View Controller placed explicitly together as one entity:

mvvm-b27768df Source: https://www.objc.io/issues/13-architecture/mvvm/

VIPER espouses the same - View Controllers comprise the V in VIPER, as demonstrated by the originators' code examples. The underlying observation being that Views and View Controllers are too tightly entwined in Cocoa to conceptualize them as separate.

What to do? Introduce other actors that handle the logic of retrieving Models and others that handle parsing Model data into what's needed of the View. If your View is doing something not UI related: introduce another actor.

Don't Empower Views to Do Anything but UI Work

There's a related idea nestled in there - that Views shouldn't even be exposed to things that allow them to do anything but their UI work. This may mean not passing them Model objects - but rather simple data objects tailored to have only the data that that View needs.

That last point is rather illuminating when you're used to seeing code like this any given day:

class MyViewController: UIViewController, UITableViewDataSource {
	
	var models: [MyModel]?

	// ...

	func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCellWithIdentifier("MyTableViewCell") as! MyTableViewCell
            let model = self.models[indexPath.row]
        
            cell.layoutWithModel(model)
        
            return cell
    }

}
struct MyModel {
	
	let name: String
	let sensitiveBusinessProperty: Int
	// ...

	func importantBusinessFunction() {
		//...
	}

}

In this example, the cell is handed the Model directly in order to lay itself out. The danger here comes in that the View has access to data, and potentially functions, that have nothing to do with it; the temptation becomes to update Model values directly or call functions on that Model, which affect other areas of the application in unknown ways. If logic is spread out like this repeatedly, understanding how data and how the Views that rely on it get updated becomes increasingly difficult, and eventually impossible.

Reduce Shared Data

Which leads us to our next theme - reducing shared data. Shared data is the source of innumerable bugs and developer expletives -- it's no surprise that these architectural patterns emphasize honing in on the flow of data and on objects whose responsibility it is to modify data.

You'll see with ReSwift or Ziggruat the term One-Way Data Flow (or Unidirectional Data Flow). What each pattern illustrates is a chain of actors that are only allowed to receive data in one way and pass it along in another. In action, a view will receive input, pass data along to another object, which parses out from the data what should be passed to another object, and so forth - until a new object representing the View's state is created and the View is made to use it and update.

What this all points away from is our typical assortment of semi-random objects - View Controllers, table cells, utilities - that share and modify the same object.

Immutable Data Objects

What you'll see a lot in examples of these architectures being used is immutable data objects being passed to and from objects. This is by design -- and it's even in the design of Swift! Swift itself has an emphasis on `struct`s, as well as clear callouts of what properties and functions modify data (think of the `var` and `mutating` keywords). The idea is to be very aware where data can be modified - and to favor using immutable objects to transfer data.

Look to Other Programming Communities for Good Architectural Patterns

ALL of these patterns take cue from patterns created by other programming communities. MVVM originated in the .NET (Microsoft!) community and shares its concept with the Presentation Model pattern described by Java extraordinaire Martin Fowler. VIPER is an adaptation of The Clean Architecture by the multi-disciplined Robert C. Martin. ReSwift and Ziggurat are adapted from the JavaScript framework Redux, which was adapted from Facebook's Flux framework.

We need to look to other communities -- be it .NET, Java, web languages, multi-disciplined developers -- who have been solving the problems we have for longer. We have to keep searching for what it is to write good code and write good applications. Simply because a trusted resource like Apple recommends a solution doesn't mean it's the best or the best in all cases.

And That's Not All

Working towards any or all of these themes will produce code that has less tangible benefits: code will be easier to debug, have better organization, be quicker for newer developers to comprehend, and of course, be easier to test. With that, I urge you to explore some of these themes in your existing codebase and to keep them at hand when you embark on your next project.

Resources

Good reads on architectural patterns:





This blog is licensed under Attribution-Noncommercial-Share Alike 3.0 Unported license.