How VIPER Is Cleaning Up Our Codebase

Jack Smith
Pion
Published in
7 min readOct 2, 2020

When I first heard of VIPER I thought it sounded like overkill. How can a design pattern described by a 5 letter abbreviation possibly simplify my code!?

Around 4 months later, I can safely say I have joined the VIPER team, and it’s now my go-to design pattern for iOS. In this article I want to talk about our implementation of VIPER at Student Beans and how it helped us to clean up a large codebase that was almost entirely MVC.

Prerequisites:

  • You are an iOS developer that is aware of several design patterns including MVC and VIPER. You don’t need to be an expert in VIPER but you need to understand it conceptually.
  • If you’re completely new to VIPER I would recommend reading an introduction or two (you could start here or here) before reading this one.
  • You have an understanding of unit tests in iOS.
  • This article follows an example project which can be found here. We recommend that you clone it and glance through it to provide some context.

Code Organisation

VIPER is organised in to modules, which most of the time relates to a specific screen; e.g. a login screen is governed by a login module. Each module will always have the components outlined here:

View: Comprising of a ViewController and any of its views. The View notifies the presenter of user actions and displays data provided by the presenter.

Presenter: Responds to user actions by asking the Router or Interactor to complete a task. Informs the view to update with data when provided by the Interactor.

Router: Handles all navigation including the displaying and dismissal of alerts.

Interactor: The home for all business logic performed by the current module. Interacts with shared Entities outside of the module such as API Clients and the Keychain. Passes back models and data to the Presenter.

Entity: Represents any service that the Interactor uses to process and fetch data. Can be a shared component used throughout the app.

Thinking in terms of modules gave us a simple and almost obvious way to start organising. I’ll use the login screen example to illustrate. Let’s start with our folder structure. We create a folder called Login for all our login module stuff, which we contain in a folder called Modules along with other modules such as Sign Up and Home. Modules now contains the logic governing any particular screen.

You’re also likely to have code that is used across modules; extensions, resources, API clients, reusable UI elements, etc. It’s up to you how you organise this code into folders and files, but the key here is that we split up shared resources and VIPER modules. Our Modules folder gives us a snapshot of every screen in the app at a quick glance, and is super easy to navigate.

Take another look at the screenshot and notice the file structure within Login, If you’re familiar with VIPER most of these files won’t be anything new, but we have added a couple of extra files to help us get more organised. Namely: LoginModule.swift and LoginProtocols.swift.

LoginModule.swift contains logic for building the module. This may include injecting any dependencies and any other pre-presentation setup. Essentially, this file helps us adhere to the single responsibility and dependency inversion principles.

However, it’s the LoginProtocols.swift file that helps us get better organised:

LoginProtocols.swift is where we set the protocols for all components of the module, exposing every interaction between these components. We do this rather than adding protocols in the same file as classes, for example declaring: LoginViewProtocol, and LoginViewController all within ModuleViewController.swift as you might do with other protocols.

The beauty of this approach is that there now exists a single place where we can at a glance, see every single interaction between our files in that module. This is fantastic for gaining an understanding of a codebase you’re studying for the first time, visualising the control flow of a module, and unit testing, which I will come back to later.

Templating VIPER

Once we had a good file structure, it then made sense to automate writing all the boilerplate code each time we wanted to create a new module. Why go through the pain of writing boilerplate code time and time again when we have XCode templates at our disposal!? If you haven’t created an XCode template before, you can find out how to do so, here.

We’re not just limited to capturing our VIPER structure, our template can also include functionality. For example, we make every new router conform to a DefaultRouterProtocol which gives every presenter easy access to a method for displaying alerts whenever we need to do so. Any common behaviour that will be used in the majority of new modules should go in here, this is another way we can speed up development of a new module.

Time and efficiency aren’t the only reasons you should consider templating your VIPER modules. Some welcome side-effects include:

  • Ease of adoption by other team members, especially new team members that could be unfamiliar with VIPER.
  • Code consistency. You are also enforcing a style, why not throw in your marking schema? for example:
// MARK: Properties
// MARK: Lifecycle
// MARK: Public Methods
// MARK: Private Methods

Unit Testing with VIPER

Another one of the motivations for us moving to the VIPER design pattern was testability. The test coverage of the project was previously quite low and we had grandiose visions of improving it so we can develop in a safer way in the future.

When using MVC, it can be pretty difficult to test individual methods and their outcomes, VIPER makes this super easy. Because each component of a VIPER module is governed by a protocol. This makes them easily mockable and we can use these mocks to test if the correct actions are completed by a particular method:

By mocking our VIPER components in this way, we can easily test each method called when a chain of events is triggered. For example;

When user taps a Log In button the presenter is notified…

The presenter then tells the interactor to perform the log in request…

A successful log in calls the correct presenter method…

Finally the UI is updated and the user is taken to the home screen.

What makes our VIPER implementation even easier to test is the ModuleProtocols.swift file found in every module. These protocols are what power our mocks, and the file itself acts as a blueprint for what needs testing within each class. If a VIPER class has a method in here, it should be tested, and it should be mocked for testing the resulting actions of another method. Of course, your code might have other testable components, but if you follow this rule, you will find your test coverage increasing rapidly in no time!

The MVC to VIPER Clean-up

So how has this implementation helped us clean up our own codebase?

I joined Student Beans in the middle of a refactor from a product that was for the most part MVC. At the same time we are adding a lot of features and working on many in-app experiments, so finding the right opportunities to refactor our codebase bit by bit was important. Refactoring from a relatively basic design pattern like MVC sounds pretty daunting, but it really isn’t.

The trick was to approach each screen as a separate refactor. When we get given a task to change a feature or implement a new one we look for the opportunity to refactor alongside. This way our time is spent improving the codebase and implementing new features simultaneously. Let’s say we decide to add a sizeable new feature to one of our legacy screens, you can be sure that we will say goodbye to the MVC pattern used in our legacy code, and rebuild using VIPER. This is generally how it goes:

  • Using our template we initialise the boilerplate for the new module.
  • From here we want to replicate the behaviour of our legacy code. We do this by splitting the public method signatures used in the our legacy code across LoginProtocols.swift. Our classes will then demand we conform to their corresponding protocols.
  • Then we will fill in the gaps with any private methods needed in each module.
  • Now we can add the new feature.
  • Lastly we mock our new module and add unit tests.

This improves our code in many ways:

  • It is more readable. An understanding of how this module works can be gained quickly by reading through the Protocols.swift file. This is especially useful for new team members who need to quickly understand the interactions between components and modules in a given project.
  • It has a better structure. During the recent refactor of a module we took a ViewController solely responsible for a screen from 2275 lines of code, to a VIPER module where the largest file was only 569 lines of code. It’s now much easier to locate code relating to a specific task; screen interactions and layout code is found in the ViewController, updates are given through the Presenter, and business logic is found in the Interactor, and so on and so forth.
  • It has far more tests. We can mock each VIPER component of the module, test method outcomes, test interactions between modules, and thus achieve excellent coverage. We still have a long way to go, but we have added over 35% test coverage to a relatively large codebase in around 18 months!

--

--

Pion
Pion

Published in Pion

This is a place for Product, Design and Engineering to tell their stories. We have hundreds of years of professional experience and we’re ready to share our ideas with the world. (formerly Student Beans)

Jack Smith
Jack Smith

Written by Jack Smith

iOS Developer @ Student Beans