MVP Architecture
MVP is an architectural pattern, a derivation of the Model–View–Controller. It’s represented by three distinct components: Model, View and the Presenter. It was engineered to facilitate automated unit testing and improve the separation of concerns in presentation logic.
In examples you’ll find a simple project built with MVP pattern in mind.
Dog.swift
Section titled “Dog.swift”import Foundation
enum Breed: String { case bulldog = "Bulldog" case doberman = "Doberman" case labrador = "Labrador"}
struct Dog { let name: String let breed: String let age: Int}DoggyService.swift
Section titled “DoggyService.swift”import Foundation
typealias Result = ([Dog]) -> Void
class DoggyService {
func deliverDoggies(_ result: @escaping Result) {
let firstDoggy = Dog(name: "Alfred", breed: Breed.labrador.rawValue, age: 1) let secondDoggy = Dog(name: "Vinny", breed: Breed.doberman.rawValue, age: 5) let thirdDoggy = Dog(name: "Lucky", breed: Breed.labrador.rawValue, age: 3)
let delay = DispatchTime.now() + Double(Int64(Double(NSEC_PER_SEC)*2)) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: delay) { result([firstDoggy, secondDoggy, thirdDoggy]) } }}DoggyPresenter.swift
Section titled “DoggyPresenter.swift”import Foundation
class DoggyPresenter {
// MARK: - Private fileprivate let dogService: DoggyService weak fileprivate var dogView: DoggyView?
init(dogService: DoggyService){ self.dogService = dogService }
func attachView(_ attach: Bool, view: DoggyView?) { if attach { dogView = nil } else { if let view = view { dogView = view } } }
func getDogs(){ self.dogView?.startLoading()
dogService.deliverDoggies { [weak self] doggies in self?.dogView?.finishLoading()
if doggies.count == 0 { self?.dogView?.setEmpty() } else { self?.dogView?.setDoggies(doggies.map { return DoggyViewData(name: "\($0.name) \($0.breed)", age: "\($0.age)") }) } } }}
struct DoggyViewData { let name: String let age: String}DoggyView.swift
Section titled “DoggyView.swift”import Foundation
protocol DoggyView: NSObjectProtocol { func startLoading() func finishLoading() func setDoggies(_ doggies: [DoggyViewData]) func setEmpty()}DoggyListViewController.swift
Section titled “DoggyListViewController.swift”import UIKit
class DoggyListViewController: UIViewController, UITableViewDataSource {
@IBOutlet weak var emptyView: UIView? @IBOutlet weak var tableView: UITableView? @IBOutlet weak var spinner: UIActivityIndicatorView?
fileprivate let dogPresenter = DoggyPresenter(dogService: DoggyService()) fileprivate var dogsToDisplay = [DoggyViewData]()
override func viewDidLoad() { super.viewDidLoad()
tableView?.dataSource = self spinner?.hidesWhenStopped = true dogPresenter.attachView(true, view: self) dogPresenter.getDogs() }
// MARK: DataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dogsToDisplay.count }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell") let userViewData = dogsToDisplay[indexPath.row] cell.textLabel?.text = userViewData.name cell.detailTextLabel?.text = userViewData.age return cell }}
extension DoggyListViewController: DoggyView {
func startLoading() { spinner?.startAnimating() }
func finishLoading() { spinner?.stopAnimating() }
func setDoggies(_ doggies: [DoggyViewData]) { dogsToDisplay = doggies tableView?.isHidden = false emptyView?.isHidden = true; tableView?.reloadData() }
func setEmpty() { tableView?.isHidden = true emptyView?.isHidden = false; }}Remarks
Section titled “Remarks”Components:
- Model is an interface responsible for the domain data (to be displayed or otherwise acted upon in the GUI)
- View is responsible for the presentation layer (GUI)
- Presenter is the “middle-man” between Model and View. It reacts to the user’s actions performed on the View, retrieves data from the Model, and formats it for display in the View
Component duties:
|Model|View|Presenter |---|---|---|---|---|---|---|---|---|--- |Communicates with DB layer|Renders data|Performs queries to the Model |Raising appropriate events|Receives events|Formats data from Model ||Very basic validation logic|Sends formatted data to the View |||Complex validation logic |||
Differences between MVC and MVP:
- View in MVC is tightly coupled with the Controller, the View part of the MVP consists of both UIViews and UIViewController
- MVP View is as dumb as possible and contains almost no logic (like in MVVM), MVC View has some business logic and can query the Model
- MVP View handles user gestures and delegates interaction to the Presenter, in MVC the Controller handles gestures and commands Model
- MVP pattern highly supports Unit Testing, MVC has limited support
- MVC Controller has lots of UIKit dependencies, MVP Presenter has none
Pros:
- MVP makes UIViewController a part of the View component it’s dumb, passive and…less massive ;]
- Most of the business logic is incapsulated due to the dumb Views, this gives an excellent testability. Mock objects can be introduced to test the domain part.
- Separated entities are easier to keep in head, responsibilities are clearly divided.
Cons
- You will write more code.
- Barrier for unexperienced developers or for those who don’t yet work with the pattern.
