Learn how to write scalable iOS code using the VIPER architecture with some MVVM and MVC tricks and coordinators in mind.
TL;DR: just download the VIPER(B) sources from gitlab
Swift design patterns and iOS architectures
A software design pattern is basically a generic template of how to solve a particular - but usually local - situation. Achitectural patterns have bigger impact on the whole codebase, they are high level generic templates. Please remember one thing:
there is no such thing as a bad architecture
The weapon of choise only depends on the situation, but you know everything is relative. Let's walk through all the iOS design patterns and architectures real quick and learn VIPER for real. 🐍
Swift design patterns
Let's start with the basics, right? If we don't get into UIKit, we can find that there are many design patterns invented, maybe you know some of them already. But hey, since we don't have that much time and I'd like to talk about VIPER, let's check out the basic principle of building UIKit apps using the MVC pattern.
The Model-View-Controller (Massive-View-Controller) pattern is a basic concept. You have usually a huge UIViewController subclass that controls all the views and collects every model that needed to be displayed for the end user. For example you call an API endpoint using URLSession or Alamofire from the controller, do the response data validation and formatting then you implement your table or collection view delegates on the view controller, so basically all the application logic goes inside that single overstuffed miserable view controller class. Does this ring a bell for you? 🙄
After realizing the problem, the first thing that you can do is outsourcing the data transforming or binding part to a sepearate class. This is how the smart people at Miscrosoft invented the Model-View-ViewModel architecture pattern. Now you're one step closer, your data models and the views can have their "get together" on a whole new level inside shiny new files far-far away from controller land. However this pattern will not clean up all the leftovers inside the view controller. Remember that you still have to feed the view controller with data, handle all the different states.
What if we move out all the data loading and presentation stuff from the view controller and put it into a new class magically called the Presenter? Sounds good the view controller can own the new presenter instance and we can live happily ever after. C'mon people we should really rename this to the Most Valuable Pattern ever! 😉
The Coordinator pattern
Say hello to The coordinator by Soroush Khanlou. Or should I simply call this as the Inverse Model View Presenter pattern? Look, here is the deal, coordinators are on a whole new level inside of this evolution progress, but they also have too much to do. It's against the Single Responsibility principle, because now you have to manage the presentation context, the data storage, the routing and all the differnet states inside coordinators or sub-coordinators... but, finally your view controller is free from all the leftover baggage and it can focus directly on it's job, which is? 🙃
To be fucking dumb.
Presenting views using UIKit related stuff, and forwarding events.
Ok, are you still with me? 😬
The VIPER architecture
First of all DO NOT believe that VIPER is bad, because it's not. I think it's a freaking amazing architecture! You just have to learn it properly, which is hard, because of the lack of good tutorials. Everyone is comparing architectures, but that's not what people should do. As far as I can see, an MVP is good for a small app with a few screens, you should never use VIPER for those apps. The real problem starts if you app grows and more and more components get into the game.
If you are planning to write a small app, just start with MVC. Later on you can fix the massive view controller problem with MVVM, but if you want to take it one level further you can always use MVP or the coordinator pattern to keep maintainability. Which is completely fine, until you realize one day that your code is stuffed with utility classes, managers, handlers and all the nonsense objects. Sounds familiar? 😅
As I mentioned this before there is no such thing as a bad architecture. There are only bad choices, which lead us to hardly maintainable codebases. So let me guide you through the most useful design pattern that you'll ever want to know in order to write truely scalable iOS applications: VIPER with builders = VIPER(B)
The VIPER architecture is based on the single responsibility principle (S.O.L.I.D.) which leads us to the theory of a clean architecture. The core components or let's say layers of a VIPERB module are the following ones:
It's the interface layer, which means UIKit files, mostly UIViewController subclasses and all the other stuff. Views don't do anything that's related to business logic, the're just a presentation and event forwarding layer which is used by the presenter. Because the view is just a pure view controller, you can use MVVM principles or data managers to make your project even more concise.
The interactor is responsible for fetching data from the model layer, and its implementation is completely independent of the user interface. It's important to remember that data managers (network, database, sensor) are not part of VIPER, so they are treated as separate components, coming outside from VIPER module land. The Interactor can prepare or transform data, but for the data management layer. For example it can do some sorting or filtering before asking the data manager to save the data. But remember that the Interactor doesn’t know the view, so it has no idea how the data should be prepared for the view, that's the role of the Presenter. 🙄
UIKit independent class that prepares the data in the format required by the view and take decisions based on UI events from the view, that's why sometimes it's referred as an event handler. It's the core class of a VIPER module, because it also communicates with the Interactor and calls the router for wire-framing. It's the only class that communicates with almost all the other components. That's the only job of the presenter, so it should not know anything about UIKit or low level data models.
Plain model classes used mostly by the interactor. Usually I'm defining them outside the VIPER module structure, because these entities are shared across the system.
The navigation logic of the application using UIKit classes. For example if you are using the same iPhone views in a iPad application, the only thing that might change is how the router builds up the structure. This allows you to keep everything else, but the Router untouched. It also listens for navigation flow changes from the presenter, so it'll display the proper screen if needed. Also if you need to open an external URL call UIApplication.shared.openURL(url) inside the Router because that's also a routing action, the same logic applies for social media sharing using UIActivityViewController.
What's the role of the Router? Yep, it's against SOLID principles if the same class has two responsibilities. So that's why module builers were invented. Basically it moves out the module building responsibility from the wireframe objects. Which is good, now you dont have to call all the routers as wireframes, you can call them routers and use builders to do the module setup. Totally makes sense. 👍
+1: NOT everything fints into a VIPER module
For example if you want to create a generic subclass from a UIViewWhatever please don't try to stuff that into the layers above. You should create a place outside your VIPER modules folder and put it there. There will be some use cases with specific classes that are better not to be VIPERized! 😉
How to write VIPER code?
Never start to create a VIPER project by hand, you should always use a code generator, because you'll need lots of boilerplate code for each module. There are many generator solutions, but I made my own little Swift script, which completely sucks, but it's really straightforward and does the job.
I'm calling it: VIPERA! (Hungarian name of the viper snake... haha 😂)
It's available inside the example repository, you can get it from gitlab. Just fire it up with the
./vipera.swift ModuleName command. It'll generate a module template with the given name in the current directory. You can change the source code as well, it's a dummy swift script, nothing special.
The anatomy of a module is pretty simple, I'm not creating subfolders for each layer, but I'm separating the interfaces and the default implementation in different places. That gives me a little bit of sanity, because most of the VIPER templates seemed to be very deterrent just because of the project structure.
Protocols are defined for almost every VIPER component. Every protocol will be prefixed with the module name, and it won't have any other suffix except from the layer name (like MyModuleRouter, MyModulePresenter).
Default implementation is used for the basic scenario, every protocol implementation follows the ModuleName+Default+Layer naming convention. So for example MyModuleDefaultRouter or MyModuleDefaultPresenter.
Communication using delegates
The flow is something like this:
Router <-> Presenter
The presenter can send events for the router using the router protocol definition.
Presenter <-> Interactor
The interactor can notify the presenter through the presenter's interface, and the presenter can call the interactor using the defined methods inside the interactor protocol.
Presenter <-> View
The view usually has setter methods to update it's contents defined on the view protocol. It can also notify the presenter of incoming or load events through the presenter protocol.
Data transfer between modules
Imagine a list, you select an item and go to a new controller scene. You have to pass at least a unique indentifier between VIPER modules to make this possible.
It's usually done somewhat like this:
- The view calls the didSelect method on the presenter with the id
- The presenter forwards the id to the router using the routeFor(id) method
- The router calls the builder to build a new module using the id
- The builder builds the new module using the id
- The router presents the new module using it's view (controller)
- The new module passes the id for everyone who needs it (router, presenter)
- The new module's presenter gets the id
- The new module's interactor loads the data and gives it for the presenter
- The new module's presenter gives the data for the view and presents it
- Detail screen appears with proper data.
NOTE: if you are presenting a controller modally you can also pass the original router as a delegate, so you'll be able to close it properly if it's needed. 😎
Long story short:
- The builder holds noone.
- The router keeps a weak reference of the view and the presenter.
- The presenter holds the router and the interactor strongly
- The interactor keeps a weak refernece of the presenter
- The view keeps a strong refernece of the presenter
- UIKit holds the views.
You should check this in the provided example, no leaks - I hope so - everything gets released nice and smoothly after you go back or dismiss a module. 🤞
Final conclusion: should I learn VIPER?
Although VIPER is highly criticized because of it's complexity, all I can say it's worth the effort to learn its priciples properly. You'll see that there are way more benefits of using VIPER instead of ignoring it.
- Simplicity - for large teams on complex projects
- Scalability - simultaneous work seamlessly
- Reusability - decoupled app components based on roles
- Consistency - module skeletons, separation of concerns
- Clarity - Single responsibilities (SOLID)
- Testability - separated small classes, TDD, better code coverage
- Interfaces - module independence, well defined scopes
- Bug fixing - easier to track issues, locate bugs and problems
- Source control - smaller files, less conflicts, cleaner code
- Easy - codebase looks similar, faster to read others work
- Verbosity - many files per module
- Complexity - many protocols and delegates
- On-boarding - lack of proper VIPER knowledge
- Engagement - VIPER is bad, because it's complex, meh!
So let's go grab the example VIPERB repository and do some real coding! 👨💻
- iOS Project Architecture: Using VIPER
- VIPER architecture: Our best practices to build an app like a boss
- The Good, The Bad and the Ugly of VIPER architecture for iOS apps
- VIPER to be or not to be?
PS. sorry about the snake... 😅