Many design patterns guide iOS architecture. People have varying opinions on what constitutes good app architecture in iOS.

I, for one, have issues with all the modern iOS architecture “best practices.”

Their main problem is that they only focus on massive view controllers. While this is a problem that we need to address, it’s far from being the only one.

After years of working in many teams with diverse backgrounds, I created the Lotus MVC Pattern to address these issues.

I have taught this new pattern to my students and email subscribers for some time now. I recently presented it at MobileFest in Kiev, and this is the first time I show it publicly in a detailed article.

Contents

Section 1

The Problems of Popular iOS Design Patterns

Section 2

The Lotus MVC Pattern

Section 3

Case study: the Lotus MVC Pattern in action

Section 1:

The problems of
popular iOS design patterns

In this section, I will analyze in detail the most popular iOS design patterns: MVC, MVVM, and VIPER.

But I will look at them from a point of view that I rarely see discussed. This will highlight the five problems an architectural pattern needs to address.

Years ago, in 2011, I worked for an agency that made apps for big clients. One of these was an investment bank in Amsterdam that wanted an iPad app to allow clients to browse their portfolios.

It was a quite complicated app, with many screens, charts, and interactive features.

Instead of the classic REST API returning JSON, I had to work with SOAP API and parse XML data. And, since it was a banking app, it needed extra security features.

Banks are very risk-averse businesses, and rightly so since they manage people’s money. So my final code had to go through a third-party review before the app went live.

Even though at the time I had only three years of experience in making iOS apps, I felt quite confident in my abilities. I followed all the “best practices” of the MVC pattern, so I expected the reviewers to recognize the brilliance of my code.

But the verdict was different than I expected.

Although the code base was solid and the app worked well, the conclusion was that my code was:

hard to read,

hard to maintain, and

hard to test.

At the time I dismissed the criticism.

For starters, unit testing was not widespread at the time in iOS, so who cared about testing? Xcode did not have any support for it, and you had to use mocking frameworks like OCMock.

On top of that, most iOS developers thought that in Objective-C we wrote better code. Reading those blogs, I was convinced of this myself.

So I thought that the reviewers didn’t understand how things went in iOS development.

The arrows in a design pattern diagram are more important than you think.

Notice in the diagram above the “update” arrow going from the model to view controllers. Many developers ignore that arrow, but it is essential. What it shows is that theexecution flowcan move up from the model layer into other layers.

Apple introduced its version of the MVC pattern at the time of Mac development. Back then, Swift, with its value types, did not exist. Model types in Objective-C were full-fledged objects.

On the Mac, it was typical to connect code using Key-Value Observing, a mechanism that triggers the execution of code when the value of a property in an object changes.

This meant that changing the state of a model object could trigger code in the observers, namely view controllers. That’s why the MVC diagram above includes that arrow.

And that’s just the start of our problems.

The many responsibilities of the model layer in the MVC pattern

The usual MVC diagram hides an important fact. In any app, there are many MVC stacks, one for every view controller.

Here the arrows going from the model layer to the view controller layer gets a new meaning. Since the model is made of objects, it also gets the responsibility of being the repository of the app’s global state.

Any app has a state which view controllers need to access. A model made of objects means that all view controllers reference the same instances. Since the model layer updates view controllers, any change to the global state propagates upwards.

When any change to the app’s state could trigger the code in other view controllers, you have a much harder problem at hand than massive view controllers.

But it does not stop there.

Model objects often access storage too, getting overloaded with responsibilities. It’s not uncommon for an object to persist its data on disk. As an example, Core Data managed objects have a reference to their managed object context.

After all, when you only have three layers, you have to put that code somewhere.

The execution flow of code in complex object graphs

Another necessary piece of an iOS app’s architecture is view controller communication.

Finally, most modern apps need parallelism in one form or another.A typical instance is asynchronous network calls.

The MVC pattern handles networking with a central networking singleton, which the general diagram does not show:

It’s clear that in an app with an already complex execution flow, parallelism is bound to introduce more complications. That’s even worse when you use singletons, primarily if you use KVO for those too.

The few layers of the MVC pattern exacerbate architectural problems

So, to recap, we have five essential aspects to keep in mind when architecting an iOS app:

separation of concerns;

state sharing;

state propagation;

view controller communication;

parallelism.

You cannot run away from these.

The problem is that, when you only have three layers in the MVC pattern, you have to think about all these all the time. The smallest piece of code can have far-reaching consequences.

Now you see what I mean when I say that massive view controllers are not the problem you think they are. They are just one of the aspects you need to consider in any app.

The problem is focusing only on one aspect, ignoring the others.

The MVVM pattern rehashes MVC without making any substantial change

The MVC pattern has, clearly, its problems. That’s why new design patterns were introduced, allegedly to solve such problems.

I don’t think they do a great job, although they do bring some interesting ideas to the table.

One of these is the Model-View-ViewModel pattern (MVVM), which is usually represented like this:

What the MVVM pattern does is add a new layer, the view model.

The idea is to put all the code that usually goes into view controllers in the view model layer instead. View models the pass data to view controllers in a format ready to be displayed on the screen, relieving them from responsibilities.

If you ever wondered why the acronym does not mention view controllers, that’s because they are part of the view layer. In MVVM, view controllers have no responsibilities and don’t take decisions. Instead, they behave like passive views.

The proponents of this pattern say that view models are more testable than view controllers. I honestly fail to see how.

But there are other ways in which MVVM is the same as MVC.

How MVVM is even more like MVC than you think

Let’s rearrange MVVM in multiple vertical stacks, as we did for MVC:

In this diagram, I am using a variation of the pattern, called MVVM+C, which adds a coordinator. That, in my opinion, is a good idea for view controller communication. We’ll get back to it later.

Again, we see that MVVM is the same as MVC:

There is still an arrow going up from the model layer to the view model layer. In MVVM the model layer is also made of objects. This makes the model responsible for state sharing again.

View models observe model objects, so state propagation also works as it does in MVC. MVVM usually uses reactive frameworks instead of native mechanisms like KVO, but there are also flavors of MVVM that don’t use RFP. In the end, though, it makes no difference. State propagates through a binding mechanism. In fact, if you dig into reactive frameworks, you can see that they use KVO. You can see that in their documentation too.

The network controller is still a singleton. Hooking it using a reactive framework again makes no difference from an architectural point of view. Observing singletons only adds problems.

Yes, I know: these are not absolutes. There are differing opinions and implementations when it comes to MVVM. Still, I checked many popular implementations, and these main ideas are widespread.

Finally, MVVM has, for me, an extra problem: it rejects storyboards and segues.

There are MVVM implementations that use storyboards, but for the UI of view controllers. They don’t use segues.

In my opinion, storyboards without segues make no sense. You might as well use Nib files and spare yourself the problems of a large storyboard file. You get the benefits of storyboards only when you use segues.

How VIPER hides familiar concepts behind uncommon terms

MVVM seems to be an unsatisfying variation of MVC. That’s where VIPER comes in.

The acronym stands for View-Interactor-Presenter-Entity-Routing (that’s quite a mouthful). The pattern is usually represented like this:

This looks completely different from MVC and MVVM. In my opinion that is, in and of itself, a problem.

The reason is that VIPER is a backronym where the names of the layers were chosen to generate a cool sounding name.

That comes at the cost of hiding the details of the pattern. Looking at the diagram, the only familiar piece seems to be the view layer.

One can’t also help wondering where the view controllers are since you can’t avoid them in iOS. Or, where is the R exactly? In the diagram, noting starts with that letter. But wait, why is the data store in the diagram but not mentioned in the name?

VIPER brings new ideas to the table at the cost of rigidity

To understand how VIPER works, it is useful again to redraw the diagram vertically:

I’m sure you can now place each component in it conceptual position more easily.

The entity layer is the model layer, renamed. But VIPER has a fundamental change: there is no arrow going up anymore. In VIPER, entities are inert. Changing them does not trigger the execution of other code.

The app’s state is also taken out of the entity layer. That’s what the data store is for. This is an excellent choice, since it separates responsibilities, making entities easier to use and to test.

Like in MVVM, view controllers are part of the view layer and have no responsibilities. But where we had view models, we now have two layers. The presenter, which manages the UI, and the interactor, which encapsulates the core, low-level functionality related to a view controller.

Wireframes have a role like that of storyboards. All the code related to the app’s navigation structure goes there. Together with presenters, they are part of the Routing layer (that’s where the R in VIPER is), and they manage the flow of view controllers on screen and their communication.

Even though I am not a fan of VIPER, I think that it’s an improvement over MVVM. It contains many useful ideas that I adopted too, even before I knew about the pattern.

It also comes with parts I don’t like though:

It still rejects storyboards and the central role of view controllers.

Like MVVM, it’s too rigid and prescriptive. MVVM prescribes a view model for each view controller. In VIPER, you always need a presenter and an interactor, even for simple screens that do not require such sophistication.

It encourages the proliferation of protocols to connect all these components (this is not evident in a diagram). While there is nothing wrong with using protocols, it can get to the extreme. When you get to the point that you need to synthesize protocols automatically, to me, you got too far.

VIPER spreads some responsibilities among more than one layer instead of placing them in a single place. Routing is such an example. This can be considered a violation of the separation of concerns principle.

Section 2:

The Lotus MVC Pattern

The MVVM+C and VIPER patterns introduce new unique problems, but also good ideas that we should not throw away.

After working for years on many apps and with developers coming from other platforms, I put together all the best ideas I gathered in a new design pattern: the Lotus MVC Patter.

The main issues of MVVM and VIPER

All the popular patterns, in my opinion, fall short on their promise of solving the problems of MVC.

In short, my main gripes with MVVM and VIPER are that:

they are too rigid and prescriptive;

they fight against the platform, rejecting the centrality of view controllers and storyboards;

they don’t respect the separation of concerns, despite their focus on the massive view controllers problem. In MVVM view models have too many responsibilities, while in VIPER these are spread thinly across objects.

Still, we should not reject their good ideas.

View controllers tend to grow too much, and there are many responsibilities that we should take out of them. To list a few: navigation, data formatting, state preservation, access to shared resources, etc.

A modular architectural pattern for iOS apps

In the past ten years, I worked on many apps: personal projects, in jobs for different companies, a startup I co-founded, and for clients.

This exposed me to many developers that had a broader background than just iOS. This gave me access to many different ideas coming from other languages and platforms.

I also read a plethora of online articles, books on architecture, functional programming and networking. I even studied other languages like Haskell, the language that everyone talks about but nobody uses, as one fellow developer once wittily put it.

This resulted in the Lotus MVC pattern, which brings all these ideas together.

I didn’t invent any of these ideas. I just organized them in a single pattern.

I have been teaching this new pattern for years to the students of my courses. This is the first time I write about it publicly, after recently presenting it a conference.

The name of the pattern is not an acronym. It merely comes from the final shape of the diagram. I had to find a cool name for it, and if they can call a pattern VIPER, I can call mine Lotus.

The name includes the MVC acronym because that’s its foundation. I expanded it to take into account the five aspects of an app’s architecture that I listed above.

The result is a modular pattern that:

respects the platform;

follows the SOLID principles of OOP;

is not prescriptive and allows you to implement only the parts that you need;

separates responsibilities clearly;

provides testable components.

View controllers remain at the center of the pattern and guide the behavior of the app

The first, fundamental aspect of MVC that my pattern preserves is the centrality of view controllers:

Let’s be honest. There is no escaping from view controllers in iOS. That is unless you want to reimplement all the functionality that they provide.

Believe it or not, I worked on projects that did precisely that. It was hell.

View controllers connect to views with the standard iOS mechanisms: outlets, the target-action pattern, and delegation.

They are not part of the view layer though, and they are not inert like in MVVM or VIPER. They take an active role at the center of the pattern and dispatch events to all other objects.

You can see in the diagram that there is no arrow going up from the model layer. That’s because value types are inert. Objects in the app get their copy of a value, which they can change safely.

Value types are also easier to reason about since they can only have one owner. There are no callbacks, no delegation, no KVO, no notification, and no asynchronous code.

The responsibilities of the model layer are limited to:

Representing the data of an app. For example, a banking app would have structures representing accounts, transactions, credit cards, etc.

Encapsulating the domain business logic, which is independent of the app. For example, in our banking app, accounts, transactions and credit cards interact with each other according to clear business logic. This would be the same in other apps, for example in the Mac or web counterparts. This makes model types reusable across projects.

One can merge the MVC roles played by an object, making an object, for example, fulfill both the controller and view roles—in which case, it would be called a view controller. In the same way, you can also have model-controller objects.

The first sentence refers to view controllers, which we already know. But the second one introduces the concept of model controllers, which I never see mentioned. Model controllers are objects that deal with the model.

This is what MVVM and VIPER try to do with view models, or presenters, interactors, and data stores, respectively.

The difference here is that model controllers are not rigidly defined. Instead, they are a layer in the pattern that you fill with objects with clear, single responsibilities, such as:

reading device sensors like the GPS, the gyroscope or the accelerometer;

scheduling network requests;

etc.

These are all model controllers that view controllers need to share since they are the single entry point to some resource (disk, sensors, network). Sharing model controller instances happens through dependency injection (more below).

But some dedicated model controllers are not shared.These support view controllers, encapsulating well-defined responsibilities. Some examples are:

data sources for table views or collection views;

state machines for complex view controller logic.

View models format data to be displayed on the screen

One of the “raisons d’être” of view models in MVVM is to convert the data to formats that the app can show to the user.

The problem though is that, in MVVM, view models also do a plethora of other things.

But the idea is sound:

Like model types, view models are inert value types. Their role is limited to:

This idea of two-way transformation is also not new. You can find it in value transformers, which existed in Mac development since Mac OS X Panther, released in 2003. Yes, it’s that old.

This idea of view models also comes from an answer Andy Matushak gave in the Q&A of the talk I linked above. He calls it view data, but I prefer to keep the word “model” in the name to make their similarity to model types explicit.

Another good idea of MVVM and VIPER is to take the navigation responsibility out of view controllers. We do that through coordinators.

Given their central role, view controllers tent to know too much about the navigation structure of an app and other view controllers. But a view controller is already overloaded with managing its screen and dispatching events.

So we move anything that is not limited to a single view controller into a coordinator.

Unlike MVVM and VIPER though we don’t reject storyboards and segues. Define navigation using segues removes a lot of declarative code that would end in a coordinator. You use segues for the same reason you use interface files instead of writing UI in code.

When the user triggers a navigation event, a view controller receives the message in its
prepare(for:sender:) method. After taking the appropriate decisions related to itself, it delegates navigation to a coordinator.

Since the coordinator sits at the center of the pattern and communicates with all view controllers, it’s also the perfect place for shared model controllers.

When navigation happens, the coordinator can inject the right dependencies into view controllers, including itself.

Without a coordinator, you can only pass shared resources from one view controller to the next, or use singletons. This causes view controllers to carry along dependencies they don’t need, to pass them to a view controller far down the line.

If you want to customize view controller transitions, you need additional objects. Namely, a transitioning delegate and an animator.

We can abstract this idea and decouple it from the implementation details. What we get are interface controllers, objects that encapsulate complex view code, removing one last responsibility from view controllers.

Since interface controllers deal with views, according to the Apple’s definition I quoted above they are also view controllers.

But view controllers in iOS are something more specific than they were in Mac development. Interface controllers are not subclasses of
UIViewController and have different responsibilities, so I gave them a distinct name.

In the pattern diagram, interface controllers are also connected to the coordinator. Like everything in the pattern, that connection is not mandatory. It’s needed only for custom transitions between view controllers.

That’s not always the case. An interface controller can handle only the interface of a single view controller with complex logic or animations.

Often, developers subclass UIKit classes to add such logic, but composition is a better solution.

If you ever added a third-party library to your app that required you to subclass their particular UIKit subclasses breaking your inheritance structure, you know what I am talking about. I will provide a concrete example below.

Section 3:

Case study:
the Lotus MVC Pattern in action

At first sight, the Lotus MVC Pattern might seem more complicated than other patterns.

There is a definite benefit in having more defined roles though. When writing code, you can focus only on subsets of components, instead of worrying about all.

In this section, I will outline how to apply the pattern to a real app.

Pick only what you need

It might seem that the Lotus MVC has many more moving parts than MVVM or VIPER. If you compare their diagram and count the actual roles though, you will find that there is not a substantial increase.

One of my goals was to avoid the prescriptive nature of other patterns. In MVVM you always need one view model for each view controller. In VIPER, you always need a presenter and an interactor.

In the Lotus MVC Pattern, you only implement the components that you need.

You need interface controllers only for complex UIs or custom transitions. Most apps don’t have either.

Simple views that do not require specialized data formatting don’t need view models either. Think of a login screen, where the username and password are plain strings.

The data source has a copy of the
Profile model type, which is a structure. This way it can break its data into rows for the table view. The view controller is relieved from the responsibility, and its code is significantly reduced.

Cells require special data formatting, so every cell has a view model. In the diagram above I only show the
SummaryCell for simplicity, but the app has a custom cell for every row in the table view. Not all of them have a view model though. The cell for the avatar does not need one since all it needs is a
UIImage.

The view controller fetches its data from the GitHub API, so it also has a reference to the network controller.

Notice that the data source has no connection to the network controller. In the general diagram for the Lotus MVC Pattern, there are no connections between dedicated model controllers are shared controllers either.

Any class that touches asynchronous code becomes asynchronous. If the data source had to retrieve its data from the network, we would have an asynchronous data source, which is harder to manage and test.

As I said, in this pattern, view controllers are the central piece coordinating all other objects. The view controller requests the profile data using the network controller and then passes it to the data source.

Elaborate table views with editable text fields

The user of the app can edit his profile and submit changes to the GitHub API.

This screen also uses a table view, so a data source is again required. In this case, though, the data in the cells do not need special formatting, so there is no view model.

We need to preserve the user’s input in the data source. Otherwise, we will lose it when the table view scrolls and reuses a cell for another row. Cells notify the view controller of updates through delegation. The view controller then updates the data source.

A typical behavior in iOS forms is to move the cursor to the next text field when the user taps on the return key. This behavior is trickier when using a table view. You need to find the next cell with a text field, scroll the table view to make it visible and make the text field become the first responder.

This is implemented through composition, using an interface controller instead of a
UITableView subclass. This allows us to reuse the interface controller across projects, without disrupting their inheritance hierarchy.

Managing the app’s navigation and the injection of shared resources

There are separate navigation flows, and view controller communication involved even between just the three screens we are considering.

Since a coordinator manages the navigation of the entire app, its code can grow pretty quickly. For that reason, the app uses a hierarchy of coordinators instead of just one.

View controller communication and the injection of the shared network controller happens through coordinators. In this way, view controllers don’t know anything about each other. They also get only the dependencies they need. For example, each view controller references just the required coordinator, instead of the whole set.

Conclusions

As we have seen, the Lotus MVC Pattern is a comprehensive design pattern that addresses all the five aspects of iOS app architecture:

It avoids massive view controllers and other monolithic classes by separating concerns across components with well-defined roles.

It simplifies the app’s execution flow by using value types for its model and avoiding binding mechanisms like KVO or reactive frameworks. (You can use those with the pattern too if you want, but I don’t recommend it).

It centralizes state sharing using shared model controllers and avoiding singletons.

It implements view controller communication and dependency injection through coordinators. At the same time, it respects the iOS platform, keeping view controllers as a central component and integrating storyboards and segues.

It addresses parallelism by isolating asynchronous code into a shared network controller. Only view controllers access the network controller, keeping all the rest of the code synchronous.

For these reasons, I think that this pattern is superior to MVVM and VIPER.

And also because, well, I created it. It would be strange if I didn’t.

If you want to see some of these ideas applied, I have a free mini-course which you can get in the form below.

Continues below

See the Lotus MVC pattern in action with my free mini course on building professional iOS apps