DEV Community

Marwan Ayman
Marwan Ayman

Posted on • Updated on

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.

Image VIPER

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 {

}
Enter fullscreen mode Exit fullscreen mode

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 {

}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

View
View is a UIViewController 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)
    }
}
Enter fullscreen mode Exit fullscreen mode

Entity

struct News {
    let title: String
    let url: URL
}
Enter fullscreen mode Exit fullscreen mode

Router

final class NewsListRouterImplementation: NewsListRouter {

    weak var viewController: UIViewController?

}
Enter fullscreen mode Exit fullscreen mode

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 {
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode
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)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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 
    }
}

Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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 using 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.

VIPER Gen
VIPER Code

Thank you for reading! If you liked this article, please Like so other people can read it too :)

Happy coding ✌️

Reach me out here

Top comments (0)