comments 13

#8 VIPER to be or not to be?

This time we would like to tell our story and share the experience about VIPER. Especially about good practices, how we deal with some specific cases and about our recommendations. We count on the comments with your experience!:)

In this article our goal is not to talk about VIPER’s rules and explain every component of VIPER from scratch. Many sources with very good explanations about these things are already available:

We would like to show what we have learnt, what have been the biggest challenges during working with this architecture. At the beginning we had many questions to answer. Last year we started a new big project: a conference application (consisting of an agenda, a participants’ list, a speakers’ list, a news’ list, etc.) and then the story began…

Ready for a fast ride with VIPER?

Viper car

Viper car

Why did we choose VIPER ?

Note: If you have doubts about which architecture to choose for your new project, maybe this site will be helpful for you. Our reasons for using VIPER:

  • VIPER architecture should be used for applications whose requirements are very well defined at the beginning. Fortunately, in our case they were. If you know that your screens and business logic can be strongly affected by a product owner, VIPER may be not a perfect solution for you. Even a small change can impact your all module files (a view, a presenter, an interactor…). Redesigning can be a very time-consuming task. A better solution would be just creating a new VIPER module.

  • Our project was quite big. By setting one module you have to generate and write a lot of (it seems sometimes) redundant code. Very often you have to just pass data between all VIPER components from a view to API manager, and then come back with some returned data to view. That’s why there is no point in using this architecture for very small projects.

  • VIPER (without a few exceptions:) ) has very clear politics about responsibility for each component. It helps with reducing amount of code in files and putting into the right place according to a single responsibility principle. Additionally a project is very well structured what enforced on every developer in our project using the same practices. If a new developer joins the team, he should really fast adapt to VIPER and from the other hand it is less possible that he destroys something in this kind of architecture:)

  • If you work in a team of for example 3 developers, then everyone can work on one module. In this way you can easily divide your work.

  • In our case we also had very good defined features and screens from the very beginning so it wasn’t a problem to translate it into VIPER modules.

  • On the basis of VIPER principles, everything in one module is very well separated, so it creates good environment for unit testing. Look at this article regarding more info about TDD in VIPER.

  • Finally, we wanted to check and try out a new architecture!!! Hoping to avoid a bad decision:

Massive

Massive

which starts from MVC and ends up with MassiveVC.

Project structure, folders and VIPER modules

Do you remember all VIPER module components? Our proposal is based on this website, where used components are called "Services". You can find more about services in a section Services.

VIPERDiagram

VIPERDiagram

How to translate this into project files? Every component should be translated into a separate folder and class:

FolderStructure

FolderStructure

Next question is: what is the best candidate for VIPER module? Basically the easiest approach is to translate one screen (or big feature) into VIPER module. For example:

  • Login screen -> Login Module.
  • Participant List -> Participant List Module

Do you imagine creating whole stack by hand every time? Fortunately, there are generators:

VIPER modules Generators

If you really want to make your application based on VIPER architecture, do not even think to make it all manually. It will be a disaster! You need an automated process to create a new module (Trust me:)) . First of all, you can download one of available VIPER generators here:

In our project we took the first one and customized it further for our purposes. For example, we have added test files for Interactor and Presenter. Our custom vipergen tool is available here:

That customization required a little bit of modifications in Ruby. Note, that it was only because we wanted to have an auto-inserted swift module name in all unit test files.

You will probably just need your custom templates. They can be easily created by copying and editing one of existing folders with templates. Just follow this instruction to learn how to add a new template. You have two options, send a PR to an existing repo or create your own fork and gem for more control. Git repositories of vipergen provide more useful information.

We did not use other solutions much, but Generamba seems like a well supported tool and provides nice setup steps from CLI. Make sure that you have checked out all available solutions to find one which one suits you best.

Sending information between VIPER modules

From the very beginning we thought how to deal with passing data between VIPER modules, there was no clear answer. It was very helpful to start reading below topics:

Finally we decided to send information from one module to Presenter in the second module (it seemed to be the best natural way without breaking VIPERs principles):

PassingData

PassingData

What does it mean in context of code?

SpeakerDetails module is initialised based on a class method invoked in SessionList wireframe, then in above method a presenter of SpeakerDetails has knowledge about what session user has selected.

VIPER Entities and Core Data

First of all, we decided to create our own core data stack. Why not to use external libraries to make this quicker? Because we wanted to have full control of our persistence store.

Our Core Data stack have two managed object contexts: one on the main thread and one on the background thread. Both connected to the same persistent store coordinator. Each context operates independently of the other. Changes are exchanged by merging the did-save notification:

CoreDataSchema

CoreDataSchema

Note: This idea was taken from a great book: Advanced Core Data, where you can find much information about how to setup your own CoreData stack with many alternatives. We strongly recommend this book:)

But what about entities?

Entities are sent between VIPER components but not as CoreData’s NSManagedObject instances. Managed objects are accessible only in local managers, where they are converted to entity ( in convertToEntity() function) and passed to an interactor:

CoreDataConvertToEntity

CoreDataConvertToEntity

Why not to send NSManagedObjects? Because in this way our application is separated from data model and data layers. In this way we keep Core Data where it should be: at the data store layer.

What data type represents entity? In our case every time it is just a struct:

which is small, immutable and thread-safe, just perfect:)

Dependency Injection

Use of VIPER architecture gives great possibility to apply dependency injection. For example, let’s consider an example of a local manager:

Injection in the constructorn in this class gave us two advantages:

  • We have a better sense what’s going on in this code. We see immediately what dependencies our class has
  • On the other hand, our class is prepared for unit testing

Another example for a presenter:

When using VIPER architecture a good practice is to use DI in every component. We will show in Unit Test section a few examples how this approach can really help us during testing.

What is really Wireframe?

In our case wireframe has two functions:

  • It initialises instances of each VIPER’s component and wires them up:

  • The second responsibility is to navigate and present other modules:

Nothing more:)

Unit testing

We have to admit that we didn’t have a big experience with unit testing before. To make first steps in this field: we started from testing interactor and presenter, because interactor contains main business logic and presenter contains logic responsible for preparing data before displaying. These components seemed to us more critical than others, but it was just our subjective opinion.

Libraries used by us for unit tests:

In VIPER every component of a module is strictly separated what creates a very friendly scenario for adopting unit tests in terms of single responsibility principle:

UnitTestMock

UnitTestMock

As we can see, by separating components in our test we can focus only on testing responsibility of interactor. The others components which talk with interactor are just mocked.

How does it look like in perspective of code?

Services

What are services? They are independent components, separated from strict VIPER components. One Service can be used in multiple modules. As it is mentioned here, services are not necessary but can be really helpful, especially in keeping high cohesion. Their responsibilities are for example:

  • dealing with Calendar (CalendarService)
  • dealing with Adress Book (AdressBookService)
  • managing Keychain (KeychainService)
  • Person Service (PersonService) – manages downloading user data like a photo etc. In this case a service has also an instance of apiClient needed for network requests.

Service in action:
Service

How to deal with listening changes from backend?

In our app we have a couple of lists to display that should be auto-refreshed. Our SynchronizerService does the heavy work of downloading and putting JSON responses into CoreData in background. It happens every 1 minute.

CoreData Update

CoreData Update

func save(notify:Bool?){
//save logic
}

class DataUpdateListener{
init(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: "dataUpdated:", name: CoreDataStackDataUpdatedNotification, object: nil)
}

}

The dataUpdated(notification:NSNotification) is overridden in interactors. There, as you may expect, standard VIPER flow kicks in. Interactor after receiving notification asks its local data manager for data. This fresh data is then passed to presenter to preprocess. Then presenter passess it to view to display. Voilá!

APIs between VIPER module components are defined in a separate protocol file so that bidirectional communication is always possible (if we define it like that). Using a closure for completion would block us from pushing data from interactor to presenter:

A good old delegate pattern works better in our update mechanism. Not only presenter can ask interactor to get data for it, but it can also initiate the whole update process:

Looks clean and solid, right?

So when to use VIPER, and when not?

The answer is always the same: it depends.

We hope that below block diagram will help you in answering this crucial question (but of course as always, this is a very individual issue):

ViperOrNot

ViperOrNot

Summarizing

Starting to use VIPER can be a big challenge, especially when it is your first project in this architecture. We hope that after reading this article most of your doubts and questions will disappear.

In our case we have practiced git flow with pull requests. It was very helpful, especially at the beginning, when every developer in project could observe carefully what colleagues were pushing to the repository. It would be a disaster, if everyone would make his own version of VIPER without consulting and watching what the others were doing. When we have inconsistency in some rule, then we immediately call the meeting to brainstorm and to choose solution together that everyone agrees to use.

VIPER determines very generally how to build an application. We encourage you to stay open minded, never stop to customize and optimize each component. Discuss each doubt with your colleges.

VIPER requires continuous improvements, we hope that in our new projects, we will do better having experience from our first project.

On every step of your development be very careful not to turn architecture into a nightmare, especially at the beginning, when you have to set up the whole project’s structure. One mistake in an architecture can turn into a chain reaction in multiplying further mistakes. That can cause a lot of tedious work.

Finally, VIPER gave us an opportunity to implement unit tests very easily, without big experience in this area:) Thanks VIPER!

What is your opinion about VIPER? Share your comments! :)

Interesting resources

13 Comments

  1. srinadh

    Thanks team, i have been looking like these details explanation on VIPER. i will read and will give my feedback. but where to we download sample project. so we can understand some more deep .

    Thanks for your effect on VIPER.

    • Michał Wojtysiak

      Hi Pawel. Good question.
      This is a perfect case to show in sample VIPER app that we plan to release.
      To explain it shortly. When there is a need to present a new ViewController we do the following:
      1. Wireframe function of presenting module has a parameter ‘fromView’ where we pass current ViewController.
      2. In this function we instantiate presented module with method that returns fresh ViewController to present.
      3. At this point we have both presented and presenting ViewControllers available in the same function body.
      That’s enough to perform various kinds of navigation.

      Best regards,
      swifting.io

    • Michał Wojtysiak

      Hi Miguel,

      We have read about other clean code architectures (uncle Bob’s comes to my mind) and appreciate their higher flexibility that VIPER provides. However we do not have practical experience with them yet. Thanks for the link to clean swift. It will be of great use when starting new project.

      Best regards,
      swifting.io

  2. Ruslan Remenkov

    One of the best articles about VIPER!
    Thank you so much! :-D

    …but I still don’t see the whole picture. Can you share simple Swift VIPER project with 2 modules: 1 will be simple, and 2nd will contains a couple of submodules with data transferring between each other?

    PLEASE! :-D

Leave a Reply

Your email address will not be published. Required fields are marked *