If you’re familiar with iOS development, there’s a big chance you know that architecture is a touchy subject. We won’t go over that particular problem here since there are a lot of good posts on the web that already cover that. Instead, we’ll talk about how we tackle this issue at Infinum.
How we got here
Apple’s proposed MVC architecture wasn’t bad for many of our smaller apps, especially when we started with iOS development. However, as our projects grew more extensive and complex, it became harder to handle many cases cleanly and consistently. So, we started searching for something else and finally came across VIPER.
When we started diving into this new architecture, our iOS team counted ten people, so we were already a bit late to the party. Now that this number has doubled, I cannot imagine working in an agency environment without a well-defined project architecture. Of course, if you are a one-man band, that doesn’t mean you don’t need a suitable architecture for yourself and all future developers coming to the project. But the need for this is even more apparent in an agency environment.
VIPER is not a silver bullet by any means, nor is any other architecture. But when you have circa 40 projects being switched between 20 people where the assignments can be anything from a 15-minute bug-fix to a couple of months worth of development, you want to have a well-defined and standardized architecture that everyone understands and uses correctly. That allows anyone from a junior newcomer to a seasoned smart-ass to know the where, what, and why, making it easier to get the job done correctly and fast.
VIPER in a few words
- View contains only view logic. It is the most reusable component.
- Interactor handles data fetching and abstracts the data store.
- Presenter connects the View and the Interactor and as such contains most business logic. It is the least reusable component.
- Entity refers to the actual models.
- Router (also commonly referred to as Wireframe) handles navigation logic, initializes and sets up the Router/Presenter/View/Interactor communication.
Where we started
There are a bunch of great posts on the web that talk about the VIPER architecture for iOS apps. Many don’t go into accurate detail about how to implement this architecture in real life.
When we started with VIPER, we tried a couple of different approaches on real-life projects. This allowed us to figure out what works and what doesn’t quickly. Here are some of the methods we tried that didn’t end up feasible:
- one master router that handles navigation throughout the whole app
- interfaces for everything, everywhere, all the time
- object references hierarchy based on object responsibilities
- writing all classes and interfaces manually
Where we wound up
After a couple of months of trial and error, we got to some excellent solutions and approaches:
- one router per module (in practice, one module refers to one UIViewController)
- use interfaces mainly between main components (View, Interactor, Presenter, Entity, Router) and other classes that require reusability and testability
- object references hierarchy based on what works well with the native technology – our module lifecycle is covered with the UIViewController lifecycle since the View has a solid reference to the Presenter, which has a reliable reference for the rest of the elements in the module
- using template code generators is maybe the most crucial point which allowed us to speed up development dramatically and also normalize the way we write these components – for instance, all our interfaces for a module are inside one Interfaces.swift file which allows you to quickly get a good grasp of the entire module’s behavior and responsibilities
These are just a couple of broad points we iterated over. There were also other minor points like naming, folder structure, standardization of navigation, data passing, etc.
We finally distilled the actual architecture implementation into templates. We open-sourced VIPER Xcode templates on Github with an in-detail description of how they should be used and why.
It is not easy to fully grasp the theory and practice behind VIPER. We think we found a way to utilize all of its merits because our implementation stays true to the theory behind it, but we also made the learning curve much less steep by using templates and well-defined rules.
The main issue with VIPER, and to some degree with other non-standard architectures, is that you sometimes have to work against the native API’s standard usage. There is no such thing as free abstraction, but we were willing to pay for ours since the pros outweighed the cons.
After more than three years of using VIPER, we’re still trying out new things now and then to stay sharp, but we’ve adopted it as a company-wide standard for any project with more than a couple of screens or lasting more than a couple of sprints. We’re still on the VIPER bandwagon and very happy to ride along.