iOS architecture: Real life VIPER
November 16, 2015
After going through all the hurdles and difficulties of iOS development as individuals, as teams and as a company, we wanted to take one step further. We always saw the same issues appear over and over again: projects, as well as best practices, evolve, developers become more confident and learn new techniques, new iOS APIs are released, others are deprecated… That’s the nature of this industry, and what makes it so exciting: it’s always changing.
The 13th issue of objc.io, simply titled “Architecture”, was the trigger of numerous conversations. We had heard of MVVM (Model-View-ViewModel) but we were looking for something more robust, a better fit for larger projects and thought, perhaps wrongly, that MVVM wasn’t the best candidate for us. So we all read this interesting article about a new architectural pattern taking on Uncle Bob’s Clean Architecture. And it had a catchy name: VIPER. It looked like a textbook example of the separation of concerns principle. At a high level, each module represents a use-case and contains 3 distinct layers to clearly delineate presentation logic, business logic and routing logic.
They often used the term PONSO (Plain Old NSObject) in the article, they may even have coined it. They are used as a means to communicate between the layers of a module. They help prevent high coupling, passing objects too far, like passing a database entity all the way up to the view layer. Instead, the entity is used by the Interactor, which in turn creates a simpler, more focused PONSO to pass to the presenter, which in turn creates another, more fit for use, to give to the view. PONSOs are small enough to be tested on a whim, yet they help simplify other parts of the module.
To further enforce the decoupling between the layers, VIPER follows the Dependency Inversion Principle. View, Presenter and Interactor are loosely coupled using interfaces (protocols).
Finally, the separation of the routing logic by using Routers seemed a novel and interesting idea. Working like the programmatic equivalent to Storyboards, they represent the flow of the app, precisely what module comes after another one.
All these were the key points that appealed to us when we reviewed VIPER. If this simple introduction whets your appetite and you want to know more, I strongly recommend you read what we now refer to as the “founding” article.
A first real life use of VIPER
The objc.io article was pretty much all we had on VIPER at the time of starting the project but we needed to interpret and implement it in order to thoroughly understand how it worked on a real project, not just in a contrived sample app. We started using it at a small scale in an existing project, on small isolated features. We liked the idea of a Presenter and an Interactor to help separate the presentation and business logic. It was a problem in our current project, and I believe a common problem for many iOS projects. After a few tries, we already started noticing patterns and establishing naming conventions. We liked the cleanliness that it added to the project.
Then I transitioned to a brand new project. New Xcode project, no legacy codebase, let’s call it Project F. We thought it was a good opportunity to use VIPER, see how it works from the start of a project. Unfortunately, the developers that joined the project had no prior experience with it. They didn’t have the context and learnings gained through the prior discussions, struggles, and implicit choices that we had made. We asked these new developers to read the article, play with the sample project and imagined they would be VIPER-ready after a week or so. We thought that a quick catch up with the developers who did play with VIPER would be enough. A quick presentation or some bullet points would do the trick. That was a mistake.
However, in a few weeks of working on a “full VIPER” project, the new team already caught-up to the level of those who had previously played with it, and quickly became even more knowledgable. We used Tech Huddles , they are a great way to build a good team spirit, and they especially helped to maintain a good consistency across the project as we all gained firsthand experience with VIPER. Each developer sharing their experience on a daily basis proved to be invaluable in the long run. They were often the place where we formalised new standards and conventions for the team.
 We call Tech Huddles the mini stand-up meetings we usually have just after the project stand-up in the morning. It’s like a tech stand-up. Developers share things like “I’m implementing a new class, soon you’ll have behaviour X for free” or “I noticed some of us name their Presenter with this naming convention, and other with this other naming convention, should we agree on one convention and stick to it?”.
Benefits of a clean architecture
Working with VIPER helped us figure out some key aspects of iOS projects. We now have a better idea of what we want when thinking of architecture, we can see what effectively adds value to a project and what helps to achieve our goals. Clear separation of the responsibilities within a module, better code coverage thanks to smaller and more readable files are the main benefits we found after using VIPER.
Clear cut blocks
Having a Presenter, an Interactor and a Router makes it more obvious what goes where. I think it’s a good step towards better compliance to the principles of Separation of Concerns and Single Responsibility. When a developer didn’t know where to put some piece of functionality, the discussion would often lead to the token question “is it presentation logic or business logic?”. And if the answer wasn’t clear, often it meant the way to solve the problem wasn’t clear. It may seem like a cookie-cutter approach and would reduce each problem to a binary Presentation/Business logic problem but we found it helpful. We found that when there was no clear answer (for infrastructure code for instance), we often resorted to separate the logic into its own “Helper” class, still making the code more maintainable.
As a result of this “clear cut blocks” approach, each class ends up doing less. Files are shorter and straight to the point. The code is more readable and refactoring is made easier. This, however, comes to a cost in terms of project structure, I’ll talk about this later. In all our uses of VIPER, our biggest files were just a bit over 400 LOC, and these are rare occurrences. The average file size is under 60 LOC.
Better code coverage
Smaller classes mean smaller test cases as well. Tests resort less to exotic tricks and are simpler to read. The barrier to entry to write unit tests is lower, so more developers write them. On Project F we also used a BDD approach for our tests (with Kiwi) so often the short, behaviour-driven test cases could be used as developer documentation. It’s quite satisfying to have self-documented code through test cases, which are less likely to go out of sync with the implementation.
Another benefit of the cookie-cutter approach discussed earlier is that all modules look the same. Any developer can maintain any module quite easily, which is not always the case on big projects. No need to learn the local convention used in a module or sift through your code base haystack to find what you’re looking for, you already know it. Does the product owner want to change some rule? You start looking in the Interactor. Do the testers report that the date didn’t format properly? You start looking in the Presenter.
Problems yet to be solved
These are the things that didn’t work well for us and we couldn’t find a better way. If we were to start a new project with VIPER we would spend some time to find solutions to these pain points.
As mentioned earlier, having smaller files means your project has to deal with more files. This is also the number one complaint from developers: VIPER is very verbose. For any module, you’re dealing with close to 10 files. For simple modules those files look uncomfortably empty, serving only to proxy calls around. And because Xcode isn’t so good at jumping to the definition of a method declared in a protocol, it makes the whole experience of navigating the code painful. It feels like overhead.
We were too committed and lacked experience so we often decided to use VIPER, even for simple modules. Should we have started new modules with just an Interactor, and add other components as they become necessary, is it the right approach? How to draw the line? Should we have had some sort of scale: if a new module is really simple, then we don’t create a Presenter at all, if it’s of medium complexity, we only create a Presenter and Interactor? I’m still undecided on what to do to solve this problem.
Developer buy-in is important, but for now, let’s imagine that all developers joining your team are thrilled to use VIPER. One of the challenges of Project F was that the team kept changing often for the first few months. People rolled off or left, new people were added to replace them or to grow the team. As a result, we had to train a total of 10 developers, all of them new to VIPER. It took each one of them around 3 weeks to pass the phase where they’re lost and don’t know which object does what and where to write this or that. It took them around a month and a half to be confident enough to contribute architecture suggestions during Tech Huddles for example.
Most, if not all of the developers were really open and curious about VIPER at first. They understood what we wanted to achieve. It was much appreciated and made my task as a Tech Lead easier. When confident and experienced enough, some of them started taking a stance, voicing what they like, or especially what they disliked about VIPER. Sometimes it wouldn’t be very constructive, but what we didn’t learn in architecture, we learned in team relationships.
The architecture of your project is a technical choice, and the developers are the technicians. We didn’t think about that when deciding to go with VIPER, but not everyone is excited about trying a new architecture. It’s important to explain to your team that your main goal may not be to ship code as fast as possible. The goals you’re setting can seem arbitrary to them and you must be prepared to defend them, or at least show empathy if they were imposed.
Even if some developers didn’t like using VIPER, it helped transpose the project’s goals all the way down to the code and gave a solid framework to implement them. When not following VIPER, developers were still pushing their limits to make the code as readable and maintainable as they could.
They are the things that annoyed us when using VIPER. If we were to do it again, we would fix them. If you start a new project with VIPER, you should think about these points first.
We said VIPER uses a lot of files and uses quite a few protocols. But how do you name them? Do you always call your presenters FooPresenter and your Interactors BarInteractor? Do you call the router Router or Wireframe? The input/output protocols explained in the founding article make sense to some and are confusing others.
This is not a deal breaker but choosing a sensible naming convention early on and sticking to it makes everyone’s life in the team easier. Naming conventions for everything. And enforce them, during code reviews for example!
We started Project F saying it’s “full VIPER”, and we stuck to that almost dogmatically. At one point, we worked on a custom UI component similar to a UITabBar, and we did it as a VIPER module. I think this is now obvious for everyone that this is not the best approach. You can do whatever you want, but eventually, your code lives in UIKit-land, and UI components don’t need to be written in VIPER.
This also comes back to the earlier point about verbosity and how to dial VIPER down or up. For lack of better knowledge, we decided to use VIPER everywhere until proven wrong. Not everything needs to be a VIPER module, not everything is a module. By being surrounded by VIPER modules, these other parts stand out more, their role is still more clearly defined.
If you make VIPER your own, perhaps you would rename it V-C-VM-M-R (View, Controller, ViewModel, Model, Router). Just kidding, that’s impossible to pronounce! But now it looks much more familiar: like a blend of MVC and MVVM with an extra side-kick. VIPER is a (first?) attempt at formalising a more comprehensive architecture. One a bit more strict than the default “Apple MVC”, vague and not very normative, which forces every team to reinvent their own ad-hoc architecture. Or if they don’t often end up with the infamous other MVC (MassiveViewController). I believe that a good design pattern should be somewhat strict and prescriptive (so you don’t do stupid things) and show you the better way. It should be flexible to accommodate the often murky real-world requirements. How to name this, where does that piece belong, what is the best way of doing this thing? We might as well follow a convention for all these small choices. So we can spend time on the real questions and write better code.
If you are talking to the kind of people who want figures and swear by metrics to evaluate any given decision, this is it.
On Project F the team wrote more than 5000 test cases, covering more than 80% of the codebase. The biggest file is less than 600 lines long. On average, view controllers are less than 160 lines long. The code is easier to read, easy to break down. And that’s not a small feat when talking of a project contributed by more than 10 developers over more than 8 months. Our bug rate was very low and developers were bashing bugs faster than QA could find them.
It wouldn’t be honest to attribute this only to VIPER, but that’s sure it forced us to think about architecture and design much more than ever before. And that’s what engineers are for: not only building but knowing how to build well.
This was also presented as a talk at YOW! Connected 2015.
Author: Jean-Étienne Parrot, Software Engineer & iOS Tech Lead