The Presentation Model

Let’s look at a common challenge in MVC, and a thirty year old solution. We’ll contrast it with MVVM, and then wrap up with tradeoffs.

We’ll use the Messages inbox as an example.

Preface: What Is and Isn’t MVC?

Stop me if you’ve heard this:

MVC is terrible! We had a massive view controller with networking, collection view layouts, and image caching. We moved away from MVC and everything was fixed!

This isn’t a strike against MVC, but our failure to teach it. When a controller contains network, storage, or layout code, it isn’t MVC. It’s ‘View and a Ball of Mud.’

The view is about presentation, including drawing, layout, and animation. The model is about the business rules, including talking to your backend. The controller is mostly glue that sits between the view and the model. It sets up the scene, and then interprets the events on either side.

Before we try a new design pattern, let’s work out the basics.

Our MVC Design

  • Model: We start with aMessageStore. It has a fetchAllConversations() method, that returns an array of Conversation objects. Each thread contains an array of Messageobjects.
  • View: A tableview with a MessageCell table view cell.
  • Controller: The datasource for the tableview.

Behind the scenes, when a new message arrives, theMessageStore receives a push notification, and checks a REST endpoint for new messages. Or not. It’s an implementation detail abstracted behind the model.

We have a requirement that each conversation in our inbox shows a preview of the most recent message. It updates when a new message arrives in a conversation. Let’s walk through the flow.

When a new message arrives, MessageStore notifies our controller. We could use a delegate or NSNotificationCenter, but it’s really up to you.

class InboxViewController:UIViewController, UITableViewDataSource {
var conversations:[Conversation] = []
var messageStore:MessageStore!
var tableView:UITableView!
  func conversationsDidUpdate(_ notification:Notification){
conversations = messageStore.fetchAllConversations()
tableView.reloadData()
}
  override viewDidLoad(){
super.viewDidLoad()
// Set up subscribers, etc
}
}

Now let’s address common problems.

Problem 1: Complex Display Rules

Let’s add a few rules around how data is displayed:

  • In that message preview, if the most recent message is a photo, display ‘Attachment: 1 Image.’ Otherwise, show the text of the message.
  • If the timestamp was today, show the time, like ‘10:00 am’. If it’s before today, show the day, like ‘Yesterday.’

“Hmm. We’re describing display, why not throw this in MessageCell?”

A few months later your company asks for an iPad app with a radically different cell design. To share these display rules, do you subclass MessageCell into PhoneMessageCell and PadMessageCell?

Then your company asks for a native Mac app. (Hey, this is fantasy.) MacOS doesn’t use UITableViewCell, so you can’t just create a MacCell subclass.

You could throw this logic inside your controller, but we’re back to violating MVC, and besides, there’s no UIViewController on the Mac.

Problem 2: Display State

Then there’s the search box. We need to keep track of the search string, but it’s a bad idea to treat UITextField's text property as the source of truth. For example, we might recycle views for performance reasons, and every iOS developer knows the self-inflicted pain of cell recycling bugs.

You could keep track of this state in the view controller, but it introduces the tight coupling from earlier.

Problem 3: Testing

Let’s say you want to test message searching, to make sure it’s a case insensitive search. With business logic inside UIViewController, you need a large swatch of dependencies in your test harness.

In addition to slowing down your test suite, your search tests might break when there’s another unrelated failure with MessageCell. Cascading test failures make it harder to track down what precisely broke.

Introducing the Presentation Model

As far back as 1988, Smalltalk developers realized there are really two types of models in the an app: the Domain Model, which deal with business logic, and the Application Model, which deals with how it’s represented on screen. The latter is bit ambiguous, so I prefer Martin Fowler’s 2004 rebranding, a Presentation Model.

For example, Message is a high level concept, and belongs in our domain model. Timestamp formatting rules, and keeping track of search text, belong in our presentation model.

For clarity, I’ll add a Presentation suffix.

class ConversationPresentation {
private var conversation:Conversation
init(_ conversation:Conversation){
self.conversation = conversation
}
  var previewText:String {
guard let lastMessage = conversation.messages.last else {
return ""
}
if lastMessage.isPhoto {
return "Attachment: 1 Image"
} else {
return lastMessage.text
}
}
}

It encapsulates Conversation and exposes previewText property to wrap formatting rules.

Rather than tie it to Conversation, we could create a ConversationPresentable protocol. In our test suite, we’d pass it FakeConversation to flex all the rules.

We could also stash search state in an InboxPresentation object. Let’s start with a bit of refactoring.

class InboxPresentation {
private var store:MessageStore
var conversations:[ConversationPresentation] = []
  init(_ store:MessageStore){
self.store = store
self.refreshConversations()
self.subscribeToNotifications()
}
private func refreshConversations(){
self.conversations = store.conversations.map { conv in
return ConversationPresentation(conv)
}
postInboxNotification()
}
func messageStoreUpdated(_ notification:Notification){
self.refreshConversations()
}
}

For brevity I’ve omitted the Notification Center details. In a nutshell, our view controller subscribes to this inbox object. When the message store updates, our inbox refreshes its conversations array.

Now we can add search behavior:

var searchText:String? {
didSet {
refreshConversations()
}
}
private func refreshConversations(){
let filteredPresentations:Conversation
if let searchText = self.searchText {
filteredPresentations = store.conversations.filter { conv in
return conv.bodyContains(searchText)
}
} else {
filteredPresentations = store.conversations
}
  self.conversations = filteredPresentations.map { conv in
ConversationPresentation(conv)
}
  postInboxNotification()
}

Our view controller just reads:

func searchFieldDidUpdate(_ sender:Any){
self.inbox.searchText = self.searchField.text
}

In addition to cleaning up our view controller, we’ve hidden details about the entities involved when filtering messages. Later we can refactor things to make a single SQL call directly to our database.

Be aware that a Presentation Model is different than a Presenter you might hear about. Apple’s MVC is technically a Model-View-Presenter pattern. For whatever reason, Apple rebranded it. If you see Presenter think Controller. If you see Presentation Model, that’s the thing we just discussed.

Strictly speaking, Fowler’s Presentation Model receives all events. You aren’t using his Presentation Model if your view controller is a delegate for a view. I consider this a minor distinction. Even within MVC, you have a spectrum between “Passive View” and “Supervising Controller.” I think there’s a much more loaded term to avoid…

What about MVVM?

If you read too many iOS blogs, by now you’re saying , “This sounds a lot like MVVM.” People keep saying MVVM, but I do not think it means what they think it means. If you’re interested in software anthropology, read on. Otherwise skip to ‘Trade Offs’ at the end.

Fowler leaves a few details up in the air:

A particular decision you have to make with synchronization in Presentation Model is which class should contain the synchronization code.

In 2005, Microsoft took Fowler’s idea and ran with it. They codified MVVM with an ambitious goal:

[MVVM] is tailored for modern UI development platforms where the View is the responsibility of a designer rather than a classic developer. The designer is generally a more graphical, artistic focused person, and does less classic coding than a traditional developer. The design is almost always done in a declarative form like HTML or XAML, and very often using a WYSIWYG tool such as Dreamweaver, Flash or Sparkle.

Microsoft admits it was inspired by the presentation model:

It looks surprisingly similar to the Presentation Model pattern … In fact, the pretty much the only difference is the explicit use of the databinding capabilities of WPF and Silverlight.

They imagined engineers would just toss a ViewModel to their designer, and the designer could build a GUI around it without writing any code. They accomplish this through the largest defining characteristic of MVVM: data bindings.

But Microsoft markets MVVM as much more than “a useful design pattern that leverages bindings.” Microsoft sells MVVM as their platform’s alternative to MVC:

In some ways the MVVM pattern is similar to the Model-View-Presenter (MVP) pattern … both patterns are variants of the Model-View-Controller (MVC) pattern, both are Separated Presentation patterns, and both are designed to isolate the details of the user interface from the underlying business logic in order to enhance manageability and testability.

This brings us to the second big characteristic of MVVM: the ViewModel replaces the Controller. It’s MVVM, not MVMVC.

The view model also provides implementations of commands that a user of the application initiates in the view.

Then there’s MVVM as misunderstood by too many iOS developers. Most iOS examples don’t use bindings— which makes sense given iOS doesn’t provide them.

In many examples, the view controller still acts as the central hub for events. That makes sense since only controllers gets essential events like viewDidAppear and rotation events. There’s no escaping MVC.

Most people say MVVM when they’re really talking about something much closer to a Presentation Model. ‘iOS MVVM’ is a reflection of a reflection. It’s WaLuigi.

Do these semantics matter?

“Does it matter that people say MVVM when they really mean presentation model? Even Apple says MVC when they really mean MVP.”

If your team already calls your presentation model a “View Model,” it isn’t worth the disruption to rename everything. For all I care, call it a Cupcake Model.

However, as you’ve noticed by me repeating “Apple’s MVC,” it’s confusing when someone co-opts an existing term. Unfortunately, it’s crazy to insist Apple renameUIViewController to UIViewPresenter in their code. But the ship hasn’t sailed for your team.

I know this is bike shedding, but if I need a suffix for these types models, I lean toward ‘presentation.’ Why?

  • ViewModel’s overloaded meaning adds noise to Google results
  • Presentation describes the object’s responsibilities. See Managers
  • It’s a tiny thing, but but “Open Quickly” produces fewer results with “pres” than “view.”

The Trade Offs

It’s important to stress that a presentation model is not a new architecture. Don’t stick it in every project and say you practice MPVC.

Consider a todo list app. You have a Task object, and decide to add TaskPresentation.

class TaskPresentation {
var task:Task
init(_ task:Task){
self.task = task
}
  var text:String {
return self.task.text
}
  var important:Bool {
return self.task.important
}
}

You end up with a lot of boilerplate with no added value. Could Swift provide tools to make this easier? Let’s not go there.

Next, we have questions like, “Would an unread property belong in the presentation layer or domain layer?” Watch out, or you’ll end up with behavior randomly split between two locations. That can be worse than the original problems.

Finally, there’s the old saying, “All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection.”

In our previous example, we had ConversationPresentation, but what if we add another presentation model for the conversation screen? We’d end up with ConversationInboxPresentation and ConversationDetailPresentation. This sounds like enterprise software.

In a real project, start with a simple set of entities. Don’t introduce a presentation model just to wrap a couple of if statements. Anyone who says “Everything must be tested” lives in a fantasy. But hey, this is subjective.


Special thanks to Kumara Krishnan for reviewing the MVVM portion of this post.