The evolution of mobile architecture Reddit
This is the first article where we talk about the architecture of the Reddit application for iOS. Here we are talking about functionality that works closer to the UI. In particular, the transition to the Model-View-Presenter (MVP) architecture . The advantages of this refactoring are:
- Improved code flexibility, clarity and maintainability to support future growth and accelerate iterations.
- Increase scroll performance 1.58 times.
- Stimulation of unit testing. The number of tests increased from a few to more than 200.
Below is an illustrative diagram of our multi-tier architecture. The first article focuses on the View and Presenter levels.
The final look of our layered architecture
Prerequisites for change
More than a year ago we published the article “Building a Ribbon in the Reddit App for iOS” . It discussed how to generate a productive, expandable tape with a remarkable record of 99.95% of sessions without failures. We explained how to use the Model-View-Controller (MVC) architecture and create abstractions for paginated data.
Now Reddit has grown and continues to grow as an organization and as a service. Consequently, the requirements for the Reddit iOS application have increased. It should support more feature requests, faster iteration cycles, and higher quality standards. The development team has grown from three to more than twenty people. The original MVC architecture hardly meets these requirements, so architectural changes had to be made.
The essence of the problem
Over time, the code lost in flexibility and clarity. In the community of iOS developers, the MVC abbreviation is often decoded as Massive View Controller, because view controllers often inflate to divine objects in more than a thousand lines. Despite all our efforts, the problem really arose: the inheritance hierarchy became uncomfortably deep, and the controllers began to turn into incomprehensible divine objects that resist change.
We drove the last nail into MVC's coffin when we decided to change the presentation level for the ribbon. Reddit’s audience is growing, so scrolling performance has become too often degraded from 60 FPS to 45–55 FPS. This means that you need to rewrite the ribbon view layer while maintaining the original implementation. But in the existing MVC architecture, we could not rewrite the ribbon view layer without duplicating thousands of lines of code.
In addition, many parts of the code base are difficult to test. The code is in the class of the presentation layer that is hard to test, and the dependencies are often in singles (singletons) or hard-coded in the class itself. We wanted to implement the possibility of normal testing.
Other tasks before refactoring: the number of failures should remain low, the new architecture should lay the foundation for future growth and not interfere with functions that depend on the existing infrastructure. That is, evolutionary, not revolutionary, changes are required.
Switch to MVP
We decided that a new version of the application is needed to solve the above problems. After reviewing several options, we decided to use the Model-View-Presenter (MVP) architecture . MVP meets all the above criteria, and it’s also a well-known and documented architecture, so it’s easier to train engineers. It also retains the concept of “view models”. If necessary, Presenter can create objects of the representation model based on the principle of sole responsibility - and use them to extend our views.
Getting rid of Massive View Controller
For iOS applications, it is assumed that view objects are subclasses of UIView, controller objects are subclasses of UIViewController, and model objects are simple ol objects. As is clear from the name of the UIViewController, here the view and the controller are combined into one object. That is, the MVC model on iOS often loses its advantages due to the rigid connection between the presentation layer and the controller. Interestingly, Apple itself recognizes this connection .
Often, the Model-View-Controller architecture under iOS turns into this.
In the MVP architecture, we take this concept into account and formalize it, considering the UIViewController really as an explicit object of the presentation layer. The concept of handling UIViewController as a view object with an unfortunate name has become popular in recent years.
So, we delete all extraneous logic in our UIViewControllers. Then we assign Presenter to the role of intermediary between the view and the model. In this new role, he does not know about view objects, such as the UIViewController. Notice that the Presenter interacts with the view through the interface. In theory, you can change the view implementation to NSViewController (for MacOS), etc.
Facilitate ViewController by introducing Presenter and sharing responsibilities
Thinking MVP options
As seen in the MVP diagram, the architecture came out very similar to MVC. Indeed, there are more similarities than differences. This architecture simply helps to establish the correct separation of presentation code and business logic , as MVC seeks. In fact, all derived MV (x) architectures, such as MVP, MVVM, MVAdapter, and others, are simply different versions of the same concept.
One may ask why we abandoned MVC at all. In fact, Apple describes different types of controllers.: for models, intermediaries and coordination. Honestly, maybe we could replace our Presenter with another controller. But they decided not to do this, because most iOS developers for several reasons formed the belief that UIViewController is a synonym for the controller. Using the word Presenter, we kind of give a signal that this object is significantly different from a regular controller with a certain set of functions and properties.
Improved flexibility, maintainability and clarity
“Prefer composition to inheritance” is a famous mantra in object programming. With inheritance, you need to predict the future and build a huge taxonomy of objects. But if your “ideally” built inheritance hierarchy begins to fall apart due to unforeseen changes, it is difficult to modify this rigid structure. In the composition, objects are created from other objects and delegate work to them. This is useful because it is easy to change the behavior of an object at run time, simply by changing the objects of which it is composed. These composite objects are also clearer, since the code is pushed out of the inheritance hierarchy into an abstraction oriented to one specific task.
Such composition is one of the main advantages that MVP architecture has given us. Now you can change the behavior of the controller simply by changing the composition of a specific Presenter. We are now less concerned about deciphering the complex and rigid structure of inheritance. Finally, presentation controllers and Presenter objects are easier to understand because they have a clearer set of tasks.
By introducing Presenter and transferring part of the logic of the view controller, we simplified the inheritance hierarchy of the controller. The figure below shows that we managed to delete the GalleryFeedViewController class, since we put all this logic in the Presenter. As already discussed, this inheritance hierarchy is easier to understand and less rigid.
Simplify inheritance hierarchy through composition
Free change of presentation layer implementation
As discussed earlier, tape scrolling performance began to decline from 60 FPS to 45–55 FPS. Therefore, for the ribbon view layer we decided to use Texture . This is an open source Apple UIKit based platform that improves interface performance by preprocessing in the background thread. In the previous MVC architecture, we could not change the implementation of the presentation layer without a lot of code duplication.
Before MVP implementation, third-party code that does not belong to View (orange) had to be duplicated in the ViewController. The
new MVP architecture made it possible to implement support for Texture, rather than rewrite things from scratch. We simply put all non-View logic into the generic Presenter class. Then they wrote a new implementation of the presentation layer c Texture and reused the Presenter code. This gave support for both implementations of View until it was time to comfortably roll out the tape with Texture for all users.
After the MVP implementation: the code that does not belong to the View is moved to the shared Presenter.
What is the result? The diagram below shows the increase in tape scrolling performance. We wanted to stay in the 60 FPS area to achieve an absolutely smooth scroll.
Of course, we implemented unit tests not only because of MVP, but it was an important factor. In particular, the MVP architecture has increased the area of testing by moving the code to a level where it is easier to check. A side effect is that the View levels are now easier - and therefore less likely to test them.
Increasing the area of testing after the transfer of code that does not belong to the View, beyond this layer,
Unit tests have improved the maintenance of the code base: they allow you to make more confident changes and help you understand what the correct behavior should be. They also make the code more flexible and understandable, because they encourage methods such as dependency injection, composition, and programming abstractions. The number of unit tests has grown from a few pieces to more than 200.
Critical analysis of MVP in Reddit
Although the transition to MVP helped a lot, but there are still some things to be learned.
The transition of the tape to the Texture caused new problems with the threads. The application did not initially support the asynchronous implementation of View. That is, errors inevitably appear in the event of a mismatch between the View state and the state of the application. For example, in the tape view there may be N records. And in the background thread, the application state has imperceptibly changed - and now contains less than N messages. If you do not resolve the inconsistency, the application will simply crash when View tries to display the Nth post in the feed.
Fixing errors with threads is the hardest. They are difficult to reproduce, so they are difficult to debug. I had to change the logic of the request and get the data to view the tape. In particular, we implemented “protection” with the prohibition of any changes to the data source, when the View of the tape undergoes some changes. This and other minor fixes have reduced the number of errors associated with stream processing. However, asynchronous multithreading can still be improved.
Secondly, the Presenter layer is an extra “step” in the pipeline. This step has a price in terms of increasing the complexity of the code and reducing performance. Sometimes you just want to execute this logic in a UIViewController on a whim or because you are used to doing so. In the worst case scenario, you find that the Presenter is present simply as an entity, without any meaningful logic. In such a situation, the Presenter does not seem to justify its existence.
Sometimes you can go from the View layer to the RedditCore layer without the participation of the Presenter
In fact, our application is not completely transformed into the MVP architecture. First, converting each individual UIViewController to a Presenter will be too time consuming — and not an evolutionary change. Secondly, as mentioned in the previous paragraph, sometimes Presenter is simply not needed. As we found in the work on implementing Texture for Ribbon, Presenter is great for facilitating massive MVC or for implementing a variable behavior View, or if you have complex logic that needs to be tested. But sometimes the UIViewController is so simple that there is no point in the Presenter. So it is optional. Presenter should be implemented only when necessary.
Summary and future plans
Refactoring the MVP architecture in the Reddit app for iOS helped solve many tasks. Having introduced the Presenter level, we gradually developed the application architecture to support the new implementation of the presentation level without disrupting other functions. The code has become clearer by facilitating the “massive MVC” - the transfer of extraneous logic into the Presenter layer. We also gave developers the opportunity to more quickly iterate and deploy new features. And significantly improved the tests.
Given all this, there is still a long way to go. We continue to create Presenter objects and improve them. You need to continue to move extraneous logic from UIViewControllers to the Presenter level. It is also necessary that all Presenters better comply with the principle of sole responsibility. In the end, both the application and the architecture evolve constantly.