If you're at all familiar with the 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 covers 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 a lot of our smaller apps, especially when we were just getting started with iOS development. But as our projects grew bigger and more complex, it became harder to handle many different cases in a clean and consistent manner. 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 more than 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 in an agency environment, the need for this is even more apparent.
VIPER is not a silver bullet by any means, nor is any other architecture. But when you have cca. 40 projects being switched between 20 people where the assignments can be anything between 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 properly and fast.
VIPER in a few words
To give a very short introduction, the VIPER architecture is an implementation of Uncle Bob's clean architecture. To put it into the context of iOS, what used to be a (possibly massive) UIViewController is now a module consisted of 5 elements:
- 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 referd to as Wireframe) handles navigation logic, initialises and sets up the Router/Presenter/View/Interactor communication.
Where we started
There are a bunch of great posts on the web which talk about the VIPER architecture for iOS apps. Truth be told, a lot of them don't go into real detail about how to implement this architecture in real-life.
When we started out with VIPER, we tried a couple of different approaches on real-life projects. This allowed us to quickly figure out what works and what doesn't. Here are some of the approaches 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 really good solutions and approaches:
- one router per module (in practice one module actually 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 strong reference to the Presenter which has a strong reference to the rest of the elements in the module
- using template code generators is maybe the most important point which allowed us to speed up development dramatically and also normalise the way we write these components - for instance, all our interfaces for a module are inside one Interfaces.swift file which allows you to easily get a good grasp of the entire module's behaviour 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 these templates on Github with an in-detail description of how these tools should be used and why. If you want to know more, it would be best to dive in there.
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 far 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.