Getting Started with the VIPER Architecture Pattern for iOS Application Development
When you are planning to build an app, one of the most important decisions is to choose how to structure your app’s core.
Better saying, you need to decide which architecture you should adopt to build all your screens, features, and contexts. You need to know how to organize your app in order to make it maintainable for the long term, testable, scalable, and understandable by anyone who enters the project sometime later. In this article, we will get familiar with a design pattern called VIPER (View, Interactor, Presenter, Entity, and Router.) for iOS development.
What is VIPER?
VIPER is an architectural pattern like MVC or MVVM, but it separates the code further by single responsibility.
One feature, one module. For each module, VIPER has five different classes with distinct roles. No class goes beyond its sole purpose.
Each of the letters in VIPER stand for a component of the architecture: View, Interactor, Presenter, Entity and Router.
- View is the user interface. This layer is the ViewController together with the
UIView
(or xib/storyboard) - Interactor contains business logic related to the data (Entities) or networking, like creating new instances of entities or fetching them from the server. For those purposes you’ll use some Services and Managers which are not considered as a part of VIPER module but rather an external dependency
- Presenter is directing data between the view and interactor, taking user actions and calling to router to move the user between views.The only class to communicate with all the other components.
- Entity is your plain data objects, not the data access layer, because that is a responsibility of the Interactor.
- Router handles navigation between the VIPER modules.
Below I will explain an example of VIPER
Protocols
I have created a separate file for all the protocols
protocol NewsListView: AnyObject {
}
protocol NewsListPresenter: AnyObject {
func viewDidLoad(view: NewsListView)
}
protocol NewsListInteractorInput: AnyObject {
}
protocol NewsListInteractorOutput: AnyObject {
}
protocol NewsListRouter: AnyObject {
}
protocol NewsListRepo: AnyObject {
}
As you can see from the above code, this is the main contract agreement between VIPER layers.
Presenter
PresenterImplementation is an implementation of NewsListPresenter protocol and confirm the NewsListInteractorOutput.
In this layer presenter has a reference object from View, Router and Interactor
final class NewsListPresenterImplementation: NewsListPresenter {
private weak var view: NewsListView?
private let router: NewsListRouter
private let interactor: NewsListInteractorInput
init(router: NewsListRouter, interactor: NewsListInteractorInput) { self.router = router
self.interactor = interactor
}
func viewDidLoad(view: NewsListView) {
self.view = view
}
}
extension NewsListPresenterImplementation: NewsListInteractorOutput {
}
Interactor
Interactor is an implementation of NewsListInteractorInput protocol
NewsListRepo is responsible to fetch the data from network or Data provider
final class NewsListInteractor: NewsListInteractorInput {
weak var output: NewsListInteractorOutput?
private let repo: NewsListRepo
init(repo: NewsListRepo) {
self.repo = repo
}
}
View
View is a view with a confirmation of NewsListView protocol and it has reference to the presenter
final class NewsListViewController: UIViewController, NewsListView {
private let presenter: NewsListPresenter
init(presenter: NewsListPresenter) {
self.presenter = presenter
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
presenter.viewDidLoad(view: self)
}
}
Entity
struct News {
let title: String
let url: URL
}
Router
final class NewsListRouterImplementation: NewsListRouter {
weak var viewController: UIViewController?
}
Now we need to update our VIPER module to fetch the news data, yes as you are thinking right now, we need to update our contract protocols first
protocol NewsListView: AnyObject {
func show(newsList: [News])
}
protocol NewsListPresenter: AnyObject {
func viewDidLoad(view: NewsListView)
}
protocol NewsListInteractorInput: AnyObject {
func fetchNewsList()
}
protocol NewsListInteractorOutput: AnyObject {
func fetchNewsListSuccess(newsList: [News])
func fetchNewsListFailure(error: Error?)
}
protocol NewsListRepo: AnyObject {
func fetchNewsList(completion: @escaping ([News]?, Error?) -> Void)
}
protocol NewsListRouter: AnyObject {
}
Let’s now add implementation for theses added funcs in the protocols
Interactor
final class NewsListRepoImplementationl: NewsListRepo {
func fetchNewsList(completion: @escaping ([News]?, Error?) -> Void) {
// Fetch the data with completion
}
}
final class NewsListInteractor: NewsListInteractorInput {
weak var output: NewsListInteractorOutput?
private let repo: NewsListRepo
init(repo: NewsListRepo) {
self.repo = repo
}
func fetchNewsList() {
repo.fetchNewsList { news, error in
if let newsList = news {
self.output?.fetchNewsListSuccess(newsList: newsList)
} else {
self.output?.fetchNewsListFailure(error: error)
}
}
}
}
Presenter
final class NewsListPresenterImplementation: NewsListPresenter{
private weak var view: NewsListView?
private let router: NewsListRouter
private let interactor: NewsListInteractorInput
init(router: NewsListRouter, interactor: NewsListInteractorInput) {
self.router = router
self.interactor = interactor
}
func viewDidLoad(view: NewsListView) {
self.view = view
// Fetch the list from the interactor
interactor.fetchNewsList()
}
}
extension NewsListPresenterImplementation: NewsListInteractorOutput {
func fetchNewsListSuccess(newsList: [News]) {
self.view?.show(newsList: newsList)
}
func fetchNewsListFailure(error: Error?) {
// show Error
}
}
View
final class NewsListViewController: UIViewController, NewsListView {
private let presenter: NewsListPresenter
init(presenter: NewsListPresenter) {
self.presenter = presenter
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
presenter.viewDidLoad(view: self)
}
func show(newsList: [News]) {
// show the news in the UI
}
}
Now, we are fetching the news list from the interactor layer, then update the presenter with the result and the presenter is prepare the data for the View and boom pass it
Last piece from the puzzle here is to build our module, so I have created a builder class to launch my module
final class NewsListBuilder {
func build() -> UIViewController {
let router = NewsListRouterImplementation()
let repo = NewsListRepoImplementation()
let interactor = NewsListInteractor(repo: repo)
let presenter = NewsListPresenterImplementation(router: router, interactor: interactor)
let view = NewsListViewController(presenter: presenter)
router.viewController = view
interactor.output = presenter
return view
}
}
Conclusion
VIPER is sounds complex in the beginning but it’s very clean architecture. It isolates each module from others. So changing or fixing bugs is very easy as you only have to update a specific module. Also for having a modular approach VIPER creates a very good environment for unit testing. As each module is independent of others, it maintains low coupling very well. So, dividing work among co-developers is also pretty simple.
My advice if you are going to use VIPER in your project, the smartest thing would be to use an automatic module structure generator. Otherwise creating files for modules will be a big hustle, There are few generators available online.
Thank you for reading!
Happy coding :v: