Memahami Design Pattern VIPER Swift

10/08/2021

Pola arsitektur menjadi suatu pendekatan metode yang umum digunakan oleh para software developer. Beberapa pola yang lumrah sekali dimanfaatkan seperti MVC (model-view-controller), MVP (model-view-presenter), dan MVVM (Model-View-ViewModel) menerapkan alur pengembangan yang cukup berbeda. Pola arsitektur (atau terkadang orang menyebut design pattern) seperti suatu hal yang wajib untuk diterapkan karena menyediakan kemudahan bagi para developer dalam membangun blue print alur pengembangan.

Pengembangan aplikasi berbasis iOS sendiri dapat memanfaatkan MVC untuk mengembangkan aplikasi iOS dengan skala kecil atau kompleksitas yang tidak rumit. Saat mengembangkan aplikasi yang diprediksi akan memiliki kompleksitas yang cukup rumit, beberapa pola umum.

Tentang VIPER

viper-diagram

VIPER (View-Interactor-Presenter-Entity-Router) adalah pola arsitektur pengembangan iOS yang memisahkan setiap kode berdasarkan masing-masing fungsionalitasnya. Fungsionalitas pada VIPER diklasifikasi berdasarkan 5 class, yaitu :

  • View

    Sesuai dengan namanya, view merepresentasikan apa yang akan dilihat olehpengguna aplikasi. Layouting dan data binding ditempatkan pada view. Data yang ditampilkan pada view diterima melalui presenter, selain itu view dapat melakukan transisi ke halaman lain atau routing melalui presenter kemudian diteruskan ke router.

  • Interactor

    Interactor melakukan proses data yang diperoleh dari API kemudian disimpan pada Entity. Peran interactor sebagai komunikator antara aplikasi dengan backend. Data yang berhasil diperoleh akan diteruskan ke view melalui perantara presenter.

  • Presenter

    Presenter berperan sebagai mediator atau penghubung antar view, router, maupun interactor. Bussiness logic pada aplikasi yang akan diimplementasi dilakukan pada presenter.

  • Entity

    Kalau MVC, MVVC, MVP memiliki model yang merepresentasi struktur data, entity pada VIPER yang berperan sebagai model. Setelah interactor berhasil mendapatkan response data dari API, interactor mengolah data tersebut kemudian menyimpannya pada entity. Entity akan dibawa presenter menuju view untuk ditampilkan.

  • Router

    Router pada VIPER berperan sebagai gerbang masuk dan keluarnya interaksi pengguna. Saat pengguna memasuki halaman, aplikasi harus melalui router, begitupun sebaliknya.

Protocol

Swift memiliki fitur protocol yang dapat diterapkan pada setiap class, enumeration, dan struct. Fungsi protocol sendiri dalam Swift membuat mekanisme atau prosedur untuk mendefinisikan method atau properties dalam sebuah class, enumeration, ataupun struct. Jadi, setiap class ataupun enum yang mengimplementasikan protocol harus menyesuaikan method atau properties yang sudah didefinisikan di dalam protocol.

viper-protocol-diagram

VIPER memanfaatkan protocol untuk menyusun prosedur interaksi antar class. Peran protocol penting untuk menjaga konsistensi alur. Class VIPER harus terdiri dari method dan properties yang sudah didefinisikan saat membuat protocol.

Penyusunan protocol ini cukup sulit diselesaikan karena developer harus menggambarkan alur secara abstrak saat inisiasi project atau module. Tapi, setidaknya langkah awal ini mempermudah ketika developer mulai melakukan coding untuk tiap-tiap class.

Alur

Oke, mulai bahas ke bagian yang sulit untuk dijelaskan. Ketika memikirkan alur ini hal yang mungkin cukup membingungkan adalah ketika memikirkan dari mana mulainya.

Tapi, salah satu cara yang bisa dicoba adalah mulai dari bagian view. Kenapa view? Di dalam view kita bisa proyeksikan fungsi-fungsi interaksi langsugng dengan pengguna. Misalnya ketika membuat halaman yang menampilkan list artikel, maka susun protocol view-to-presenter dan presenter-to-view.

Disclaimer : bagian-bagian code ini hanya sebagai prototype semata, tidak bisa di-build secara mentah-mentah 🙇🏻‍♂️

View

// /Modules/Article/Protocol/ArticleFeedsProtocol.swift
import Foundation
import UIKit

protocol ViewToPresenterArticleFeedsProtocol: AnyObject {
    var view: PresenterToViewArticleFeedsProtocol? {get set}
    var interactor: PresenterToInteractorArticleFeedsProtocol? {get set}
    var router: PresenterToRouterArticleFeedsProtocol? {get set}

    func startFetchArticleFeeds()
    func showDetailArticleFeeds(nc: UINavigationController)
}

protocol PresenterToViewArticleFeedsProtocol: AnyObject {
    func articleListSuccessFetched()
    func articleListFailedFetched()
}

Protocol ini nanti diimplementasikan ke class view, misalnya class *view-*nya seperti ini.

// /Modules/Article/View/ArticleFeedsViewController.swift
import UIKit

class ArticleFeedsViewController: UIViewController {
  // TODO : Add IBOutlets, Properties, table view or other methods here...
    var presenter: ViewToPresenterArticleFeedsProtocol?
    override func viewDidLoad() {
        super.viewDidLoad()
        presenter?.startFetchArticleFeeds()
        // Do any additional setup after loading the view.
    }
}

extension ArticleFeedsViewController: PresenterToViewArticleFeedsProtocol {
    func onArticleFeedsResponseSuccess() {
        // TODO: reload table view if you use UITableView
    }

    func onArticleFeedsResponseFailed() {
        // TODO: show error screen
    }
}

Class ini berisi methods, properties, dan IBOutlets untuk menampilkan UI dari storyboard maupun XIB. viewDidLoad() akan memanggil method dari class presenter untuk menjalankan proses pengambilan data yang nantinya interactor akan dieksekusi di dalam method tersebut.

Selain itu, pada class view ditambahkan extension yang mengimplementasi *protocol presenter-to-view*. Extension view ini akan mengeksekusi methods yang memproses data yang selesai diambil melalui interactor.

Presenter

Karena protocol view-to-presenter sudah dibuat saat membuat class view, maka selanjutnya hanya membuat protocol interactor-to-presenter. Protocol ini untuk menerima response atau data model yang telah berhasil di-mapping dari interactor.

// /Modules/Article/Protocol/ArticleFeedsProtocol.swift
protocol InteractorToPresenterArticleFeedsProtocol: AnyObject {
    func articleListSuccessFetched()
    func articleListFailedFetched()
}

Kemudian saatnya mengimplementasi *protocol view-to-presenter* dan interactor-to-presenter yang baru saja dibuat di class presenter. Di dalam class presenter, method startFetchArticleFeeds memanggil method interactor yang berfungsi untuk melakukan fetch data. Selain itu, dari class presenter fungsi routing ke halaman lain bisa dipanggil dari method presenter.

Extension presenter mengimplementasi protocol interactor-to-presenter untuk meneruskan response data dari interactor ke view.

// /Modules/Article/Presenter/ArticleFeedsPresenter.swift
import Foundation
import UIKit

class ArticleFeedsPresenter: ViewToPresenterArticleFeedsProtocol {
    var view: PresenterToViewArticleFeedsProtocol?

    var interactor: PresenterToInteractorArticleFeedsProtocol?

    var router: PresenterToRouterArticleFeedsProtocol?

    func startFetchArticleFeeds() {
        interactor?.fetchArticleFeeds()
    }

    func showDetailArticleFeeds(nc: UINavigationController) {
        // TODO : Add action or router that routing to next module/view
    }
}
extension ArticleFeedsPresenter: InteractorToPresenterArticleFeedsProtocol {
    func articleListSuccessFetched() {
        view?.onArticleFeedsResponseSuccess()
    }

    func articleListFailedFetched() {
        view?.onArticleFeedsResponseFailed()
    }
}

Interactor

Supaya presenter mampu meneruskan perintah proses fetching data dari view ke interactor, saatnya menambahkan *protocol presenter-to-interactor* dan class interactor yang berisi method fetching data.

// /Modules/Article/Protocol/ArticleFeedsProtocol.swift
protocol PresenterToInteractorArticleFeedsProtocol: AnyObject {
    var presenter: InteractorToPresenterArticleFeedsProtocol? {get set}
    var article: [ArticleFeeds]? {get}

    func fetchArticleFeeds()
}

Class interactor berisi method yang berperan untuk melakukan tugas fetching data atau integrasi dengan API. Third party seperti Alomofire dapat dimanfaatkan untuk melakukan request dan menerima response dari service API. Kemudian di class ini juga instance dari Entity class diperlukan sebagai data model yang menyimpan response data.

// /Modules/Article/Interactor/ArticleFeedsInteractor.swift

import Foundation
import UIKit

class ArticleFeedsInteractor: PresenterToInteractorArticleFeedsProtocol {
    var article: [ArticleFeeds]?

    var presenter: InteractorToPresenterArticleFeedsProtocol?

    func fetchArticleFeeds() {
        // TODO : fetch data from some API using third library like Alamofire, keep response data to ArticleFeeds
    }
}

Router

Class router mengimplementasi protocol presenter-to-router. Class router sendiri terdiri dari method untuk inisiasi modul melalui static method yang mengembalikan class view atau *view controller-*nya. Selain itu terdiri dari methods yang melakukan navigasi ke halaman lain. Berikut contohnya.

// /Modules/Article/Protocol/ArticleFeedsProtocol.swift
protocol PresenterToRouterArticleFeedsProtocol: AnyObject {
    static func createArticleFeedsModule() -> ArticleFeedsViewController
    func pushToArticleFeeds(nav: UINavigationController)
    func pushToArticleDetail(nav: UINavigationController)
}
// /Modules/Article/Router/ArticleFeedsRouter.swift
import Foundation
import UIKit

class ArticleFeedsRouter: PresenterToRouterArticleFeedsProtocol {
    static func createArticleFeedsModule() -> ArticleFeedsViewController {
        let view = ArticleFeedsViewController()
        let presenter = ArticleFeedsPresenter()
        let interactor: PresenterToInteractorArticleFeedsProtocol = ArticleFeedsInteractor()
        let router: PresenterToRouterArticleFeedsProtocol = ArticleFeedsRouter()

        view.presenter = presenter
        presenter.view = view
        presenter.interactor = interactor
        presenter.router = router
        return view
    }

    func pushToDetailArticleFeeds(nav: UINavigationController) {
        // TODO: call other module router to navigate other modules
    }
}

Entity

Terakhir, entity yang terdiri dari class, struct, maupun enumeration sebagai data model dari module. Dalam file entity bisa dibuat beberapa class, enumeration, atau struct.

// /Modules/Article/Entity/ArticleFeedsEntity.swift
import Foundation

struct ArticleFeeds : Codable {
    var title: String?
    var author: String?
    var body: String?
    var publishDate: String?
}

Source

  • https://medium.com/@smalam119/viper-design-pattern-for-ios-application-development-7a9703902af6
  • https://docs.swift.org/swift-book/