The Good, The Bad and the Ugly of VIPER architecture for iOS apps.
Introduction to VIPER
If you were developing iOS apps for some time you’ve noticed that most of your logic goes inside UIViewController’s subclasses. It’s commonly referred as Massive ViewController problem. View is rather a simple object that cares only about drawing itself on a screen. Model is a dull storage of data which can be represented by a simple collection or a complex data structure.
And basically, all other logic should go inside this poor lonely ViewController. It’s transforming model objects into some more View-friendly state and vice-versa. It’s also handling user input, fetching data, making network calls, allocating and presenting other ViewControllers. Does it fair that all this fun goes to ViewController?
It’s not only ViewController’s readability that suffers from extra weight (in lines of code) but also testability, maintainability, and refactoring speed. Considering that Swift is changing rapidly and Apple releases new iOS every year, maintaining and refactoring ViewControllers is a huge part of your job as an iOS developer. And testability here is really important cos you don’t want those scary regressions.
So some smart people decided to improve MVC (Model-View-Controller) in a way that ViewController basically goes on a diet. MVVM (Model-View-ViewModel) was the first try. It releases ViewController from the need to perform conversion between Model objects (business objects if you like) into View-friendly (let’s call them ViewModels from now on) objects. This approach improves testability and makes ViewControlles leaner, but is it enough? It does not address responding to actions, networking, navigation and so on. So people decided that they need to free ViewController from these others things as well.
First of all, they’ve created Presenter (ViewModel in case of MVVM). This class is responsible for updating User Interface. It receives business-objects and translates them into ViewModels.
Where it receives them from? From another class called Interactor which is responsible for getting business-objects from everywhere it can: network, persistence storage, and passing them into Presenter. It also responsible for responding to user actions. So when you press Login button your ViewController pass it to Interactor and it makes network call (usually, you want to do that in a separate object, more on that below), waits for the response and then decides where you need to go next.
So when Interactor makes this decision it calls another object called Router (you may find Wireframe in some articles) which performs segues or present ViewControllers modally.
So you may ask what’s left for ViewController? Apart from communication with Presenter it still manages a lot of things. Since we are playing on UIKit’s territory we are not free from its influence. I believe that you should assign your ViewController to be delegate and dataSource of various things. By doing so you won’t make some crazy things with your Interactor. Don’t try to break UIKit. It is impossible and unnecessary.
So we have V for View, I for Interactor, P for Presenter, R for Router. So what’s E in VIPER stands for? E stands for Entity. Entity is basically what you were calling Model in MVC — dull data storage. Personally, I feel that it was put in VIPER just to make it sounds cool. Your Interactor will obtain those Entities from various sources but also will make network calls or perform some long-term tasks using Services — objects that encapsulate this kind of tasks. For example, you may create TasksService in ToDoList app (every iOS developer should make one) that will make network call to the server, parse the response into business-object and give it back to Interactor. Let’s say you will find fireDate in the response. It will be similar to this format: “1994–11–05T08:15:30–05:00”, but in the View you should show something like “2 hours remaining”. Interactor passes Date object to Presenter which converts Date into String and passes it to ViewController.
But we’re missing some crucial element here. Each of your ViewController requires Interactor, Presenter, and Router. All these things communicate with each other by extensive use of protocols. So you need to assemble all these guys and make them know each other. This is done by an object called Configurator or Assembly.
So now we have everything we need to create one isolated part of VIPER architecture called Module.
You should know that there is no such thing as common VIPER module. Some developers connect Router with Presenter instead of Interactor, others use a separate object that works with Entities, etc.
We’ve been using VIPER for about 3 months. Not really long time, you might say, but it’s enough to see some pitfalls.
- VIPER is an attempt to apply SOLID principles on iOS platform. It is not only architecture but also set of rules and agreements. And it is so helpful. Once you got used to it there will be no turning back. Just give it a try you’ll like it.
- Testability out-of-the-box. Since Modules are loose coupled it is really easy to test them separately. Every object inside the Module is also separated well enough which is great for Unit Testing. 80–90% test coverage is easy to achieve comparing to MassiveViewController that is almost impossible to test.
- Since modules are independent VIPER is really good for large teams (in the case of iOS it’s more than two developers). It means fewer merge conflicts, better testability, and easier changes of modules. You may want to create initial architecture skeleton first and then give modules one by one to other developers to implement logic.
- Codebase looks similar. As long as you start to feel philosophy of VIPER you will be much faster with reading others people code. Files will be smaller (no more 3K lines of code UIViewControllers) logic will be clearer and overall stability and flexibility higher. You could even make cross-platform code reviews of business logic with Android team. How cool is that!
- VIPER does not fit into Apple’s UIKit paradigm. There is no way that you will be able to easily split your business and representation logic between Interactor and Presenter. Third-party SDK’s often does not fit into VIPER either. So you have to discuss a lot of things with colleagues, try different approaches and experiment. This actually sounds fun for Engineering Team, but try to sell it to Product Team.
- Little to nothing on documentation, best practices, and community. Apart from several articles all you have is Book Of VIPER written in Russian (only 10% is translated to English at the moment) and nothing else. Since the first article about VIPER that I was able to find was published in 2013 you might guess it is not so popular topic.
- A new developer should spend a couple of days learning the basics of VIPER and then the couple of weeks familiarizing with it. Extra time on each pull-request, more time to research and discuss. And in the end, all these problems won’t go away. After a while, you will have some set of agreements and how-to’s but there always be new challenges.
- View, Interactor, Presenter, Entity, Router, Configurator… You can guess that it’s a lot of files. Twice more with testing. It will be a waste of time making them manually each time. So smart people created automated code generator for VIPER like Generamba. It will not only help you to generate all the files but also protocols and common functions.
- Communication between Modules. In classical VIPER modules should communicate vis Presenters. I disagree with that concept it violates Single responsibility. Since the whole point of VIPER is independent modules we need to find a way to pass data between them without knowing any details about their implementation. Probably the best way to handle this is ViperMcFlurry. But if you will look inside of its code you will see that it is based on method swizzling and associated objects. Doesn’t it ring the bell that something is overcomplicated here?
- Eventually, you’ll have to build version of VIPER that suits your needs. After a while you’ll notice some pitfalls in VIPER architecture: some modules do not need View, Presenters remain unused etc. Guys at Uber did a great job and created RIBs architecture.
Instead of conlusion
VIPER looks brilliant on paper: SOLID principles, TDD in its veins, code separation, scalable and so own. But we are living in a real world. We have deadlines, we need to iterate quickly and ship our product to customers ASAP. You might say that it is product managers, designers or even customers fault. I partially agree with this. We as developers should educate managers that speed≠quality and sooner or later technical debt will become a problem. That’s why I didn’t list that in The Bad section.
VIPER is really great when you have plenty of time and your app is not changing a lot. I strongly recommend you to give VIPER a try. You’ll be much better developer after that. In your side project, maybe in some small helper app. It will be much slower at first but after some time you’ll be saving(!) time comparing to MVC. Only then start applying VIPER on your workplace.
If you have some questions, here is my Twitter or just reply here.