Andrew Rohn

This is the first in a series on the architectural work we’ve been doing on the Reddit iOS app. This first part will discuss the work we’ve done closer to the UI. In particular, we’ll discuss how our move to a Model-View-Presenter (MVP) architecture:

Improved code flexibility, comprehensibility, and maintainability for future growth and faster iteration.

Unlocked a buttery-smooth 58% improvement in scroll performance.

Encouraged unit testing and increased test count from only a handful to 200+.

For clarity, the below is a high level diagram of the layered architecture we ended up with. We’ll be focusing on the view and presenter layer in this first post.

High level: The layered architecture we ended up with.

Setting the stage

Over a year ago we published Building the Feed for the Reddit iOS App. There, we discussed how we built a performant, extensible feed with an admirable 99.95% crash-free session rate. We explained our application of the Model-View-Controller (MVC) architecture and creating abstractions for fetching paginated feeds of data.

Fast forward to today and Reddit has grown and continues to grow both as an organization and a product. Consequently, the Reddit iOS app was placed under heavier demands. The app needed to support more feature requests, faster development iteration cycles, and a higher quality standard. It also had to support a development team that had grown from around three developers to twenty-plus developers. The original MVC architecture strained under these increased demands and we needed to make architectural changes to adapt.

Illustrating the problem

The code had also grown more inflexible and incomprehensible. In the iOS development community, “Massive View Controller” is a well-known appropriation of the MVC initialism because it’s common for view controllers to balloon to one-thousand plus line god objects. Despite our best efforts, this problem began to rear its head. On top of this, our view controller inheritance hierarchy had grown uncomfortably deep. This meant our controllers began to grow into incomprehensible god objects that were inflexible to change.



The final nail in the MVC coffin that necessitated a re-architecture was when we wanted to change our view layer implementation of the feed. As the Reddit app continued growing, the feed scroll performance started to degrade from a consistent 60 fps to an all too occasional 45-55 fps. This meant we needed to rewrite the view layer of the feed while still supporting the original implementation. But, in the existing MVC architecture, we couldn’t rewrite the view layer of the feed without thousands of lines of code duplication.

Further, there existed many difficult to test parts of the codebase. Code was placed into difficult to test view layer classes and dependencies were often singletons or hard-coded into the class itself. We wanted to makes changes that would unlock the assurance that testing provides.



On top of all this, these changes needed to maintain the high crash-free session rate, lay the foundation for the next phase of growth, and work without disrupting the feature teams dependent on the existing infrastructure. This meant changes had to be evolutionary rather than revolutionary.

Going Model-View-Presenter

We decided we wanted the next iteration of the app needed to address the above problems. While we considered several options, we decided to go with a Model-View-Presenter (MVP) architecture. In addition to meeting the above criteria, MVP has the benefit of being a well-known and documented architecture which meant it would be easier to educate engineers. It also preserved the concept of “view models.” If necessary, we can create single-responsibility-focused view model objects in the presenter and use them to inflate our views.

Model-View-Presenter diagram.

Breaking up “Massive View Controller”

Commonly in an iOS app, view objects are subclasses of UIView, controller objects are subclasses of UIViewController, and your model objects are plain ol’ objects. As you can read in the name “UIViewController,” the role of both the view and controller are combined into a single object. This means that MVC on iOS tends to lose its proposed benefits because of its tight coupling of the view and controller layer. Interestingly, Apple themselves admit to this coupling.

What Model-View-Controller on iOS often ends up actually looking like. What Model-View-Controller on iOS often ends up actually looking like.

In an MVP architecture on iOS, we accept this notion and formalize it by considering the UIViewController to really just be a glorified view layer object. This concept of treating the UIViewController as a view object with an unfortunate naming has gained popularity in recent years.

So, in our UIViewControllers, we remove any non-view logic. Then, we allow the presenter to take on the role of the intermediary between the view and model. In this new role, the presenter has no knowledge of view concretions such as UIViewControllers. Note that the presenter communicates with the view through an interface. In theory, we could swap out the view implementation with an NSViewController (MacOS) or other view concretion.

What Slimming down the ViewController by introducing a Presenter and splitting up responsibilities.

Thinking through MVP

As you can see in the MVP diagram above, the architecture actually ends up looking a lot like MVC. The truth of the matter is that there are more similarities than differences. This architecture just helps us restore proper separation of presentation code and business logic that MVC aims for. In fact, descendent MV(x) architectures, such as MVP, MVVM, MVAdapter, etc., are really just different flavors of this core concept.

One might wonder further why we threw out MVC altogether. In fact, Apple describes different kinds of controllers such as model controllers, mediating controllers, and coordinating controllers. Truthfully, we likely could replace our presenter with a different kind of controller. However, we chose not to because most iOS developers, for a variety of reasons, are instilled with the belief that UIViewControllers are synonymous with controllers. By using the word “presenter” we signal to the developer that this object is meaningfully different than a typical controller with a specific set of responsibilities and properties.

Improved flexibility, maintainability, and comprehensibility

“Prefer composition over inheritance” is a well-known mantra in programming. With inheritance, you have to predict the future and construct a grand taxonomy of objects. However, when your “perfectly” constructed inheritance hierarchy begins to fall apart because of unforeseen changes, it’s difficult to change this rigid structure. With composition, you can instead compose your object out of other objects and delegate work to them. This is useful because now you can easily change the behavior of your object at runtime by simply changing the objects it’s composed out of. These composable objects are also more comprehensible because you’ve pushed code out of your inheritance hierarchy into a more single-responsibility focused abstraction.

This composability was one of the greatest benefits the MVP architecture gave us. Since our view controllers are now composed of a presenter, we can change the behavior of a view controller by simply changing the concrete presenter it’s composed of. We can also worry less about having to construct and decipher a complex and rigid inheritance structure. Lastly, presenters and view controllers are easier to comprehend because they have a more focused set of responsibilities.



By introducing a presenter and pushing non-view logic into it, we were able to flatten our view controller inheritance hierarchy. In the graphic below, you can see that we were able to remove the GalleryFeedViewController class because we pushed all gallery mode logic into a presenter. As discussed before, this flatter inheritance hierarchy is easier to comprehend and less rigid.

Flattened inheritance hierarchy through composition.

Free to change the view layer implementation

As discussed earlier, the feed scroll performance started to degrade from a consistent 60 fps to an all too occasional 45-55 fps. So, we decided to use Texture to rewrite the view layer implementation of the feed. Texture is an open source framework built on top of Apple’s UIKit that improves UI performance by precomputing work on a background thread. But, given the current MVC architecture, we couldn’t actually change out the view layer implementation without a ton of code duplication.

Before MVP: Duplicate non-view code (orange) in the ViewControllers.

Using the new MVP architecture, we were able to evolve to support Texture rather than have to rewrite things from scratch. We simply pushed all non-view logic into a shared presenter class. Then, we wrote our new Texture implementation of the view layer and reused our presenter code. This gave us the benefit of supporting both view implementations until we were comfortable rolling out the Texture feed to all users.

After MVP: Move non-view code into a shared Presenter.

So what’s the impact? This chart illustrates how much we improved our feed scroll performance. We want to stay as close as possible to 60 frames per second to achieve a buttery smooth scrolling feed.

Unit testing

Although MVP isn’t solely responsible for enabling and encouraging unit testing, it was a large factor. Most notably, MVP increases the unit testing surface area by moving code out of the harder to test view layer into the more easily testable presenter layer. Conveniently, this also has the side benefit of making our views dumber. As the views get dumber, we care less about testing them.

Increasing the testable center by moving non-view code out of the view layer.

Unit tests make our codebase more maintainable because they give us confidence in making changes and helps us understand what the correct behavior should be. They also make our code more flexible and comprehensible because they encourage techniques like dependency injection, composition, and programming to abstractions rather than concretions. Since the MVP refactor, we’ve gone from only a handful of unit tests to 200+.

A critical examination of MVP at Reddit

Though our MVP refactor work did give us a lot, there are some things to critically examine.

Re-writing the feeds using Texture introduced a new set of threading issues. The app was not originally written to support such an asynchronous view implementation. This means we’ve had issues where inconsistency between our view state and our app state introduces bugs. For example, the view of the feed might expect there to be N posts. However, on a background thread the app state has changed under our feet undetected and now contains <N posts. If we don’t correctly address the discrepancy, the app will crash when the view attempts to display the Nth post in the feed.

Threading bugs are among the hardest to fix. They’re difficult to consistently reproduce which means they’re hard to debug. We’ve had to change some of the logic for retrieving the data source for our feed views. In particular, we’ve put in safe guards to “freeze” the data source from any changes while the feed view is undergoing any sort of changes. This and other smaller fixes have cut down on the threading related bugs. However, there still remains room for improvement in improving our support for supporting a more async, multithreaded world.

Secondly, the presenter layer is yet another “hoop” to jump through. This hoop jumping does have a cost in terms of increased code complexity and decreased performance. Sometimes you might want to just execute non-view logic in a UIViewController out of luxury or habit. In a most unfortunate situation, you might find your presenter merely exists as a pass through where no meaningful logic is actually executed. In a situation like this, the cost of having a presenter doesn’t seem to justify its existence.

What MVP really looks like. Sometimes layer bridging from View to RedditCore with or without a Presenter.

In reality, the app hasn’t been entirely converted to an MVP architecture. For one, converting every single UIViewController to have a presenter would be a massive undertaking—not evolutionary. Secondly, as stated in the previous paragraph, sometimes presenters just aren’t necessary. As we discovered in the Texture feed refactor work, a presenter is great if you’re trying to slim down your massive view controller or you need to have a variable view implementation or you have sophisticated logic you want to test. But sometimes your UIViewController is so simple that you can’t justify adding a presenter. What this amounts to is that presenters are optional and the developer should implement one only when necessary.

Summary and future work

Refactoring to an MVP architecture in the Reddit iOS app helped us achieve a lot of our desired goals. By introducing a presenter layer, we were able to gradually evolve the application architecture to support a new view layer implementation without disrupting feature teams. Code is becoming more comprehensible as we slim down our “Massive View Controller”s by pushing non-view logic code into the presenter layer. We’ve also given the feature teams more leverage to quickly iterate and spin up new features. And lastly, we’ve made large strides in improving the testability of the application.

Given all that, we still have a long ways to go. We’re still building out our presenters and refining them. We need to continue moving non-view logic out of UIViewControllers and into the presenter layer. We also need to make presenters more single-responsibility-focused. In the end, the application and its architecture is—and always will be—evolving.

Up next, we’ll take an even broader look at the architecture of the iOS app. We’ll discuss the modularization of the Reddit iOS app by creating separate frameworks such as RedditCore and RedditUI. And we’ll tie this back to how this works with the MVP architecture. Stay tuned!