Mobile Development 21 min read

Choosing the Right iOS Architecture: MVC, MVP, MVVM, and VIPER Compared

This article reviews the most popular iOS architectural patterns—MVC, MVP, MVVM, and VIPER—explaining their responsibilities, testability, and maintainability, and provides concise code examples so developers can decide which pattern best fits their project requirements.

21CTO
21CTO
21CTO
Choosing the Right iOS Architecture: MVC, MVP, MVVM, and VIPER Compared

If you find MVC uncomfortable in iOS, wonder whether MVVM is worth the switch, or are unsure about VIPER, this guide answers those questions and helps you organize all your knowledge about iOS architecture patterns.

Why Architecture Matters

A poorly designed class that mixes view, controller, and model logic becomes hard to debug, test, and extend. Good architecture should distribute tasks to clear roles, be testable, and keep maintenance costs low.

Characteristics of a Good Architecture

Task distribution among clearly defined entities.

Testability derived from that separation.

Ease of use and low maintenance cost.

MVC

Traditional MVC tightly couples view and controller, leading to massive view controllers. Apple’s Cocoa MVC tries to mitigate this but still suffers from heavy view‑controller responsibilities.

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

class GreetingViewController: UIViewController { // View + Controller
    var person: Person!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }

    func didTapButton(button: UIButton) {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.greetingLabel.text = greeting
    }
    // layout code goes here
}

// Assembling of MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model

While simple, this approach makes the view controller large and hard to test.

MVP

MVP separates the view (UIViewController) from the presenter, improving testability at the cost of more boilerplate.

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingView: class {
    func setGreeting(greeting: String)
}

protocol GreetingViewPresenter {
    init(view: GreetingView, person: Person)
    func showGreeting()
}

class GreetingPresenter: GreetingViewPresenter {
    unowned let view: GreetingView
    let person: Person
    required init(view: GreetingView, person: Person) {
        self.view = view
        self.person = person
    }
    func showGreeting() {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController: UIViewController, GreetingView {
    var presenter: GreetingViewPresenter!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }

    func didTapButton(button: UIButton) {
        // delegate to presenter
        (presenter as? GreetingPresenter)?.showGreeting()
    }

    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }
    // layout code goes here
}

// Assembling of MVP
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenter

MVP offers clear separation and easy unit testing of the presenter, but the view controller still contains UI wiring.

MVVM

MVVM introduces a ViewModel that holds presentation logic and binds to the view, often using KVO or reactive frameworks.

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingViewModelProtocol: class {
    var greeting: String? { get }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set }
    init(person: Person)
    func showGreeting()
}

class GreetingViewModel: GreetingViewModelProtocol {
    let person: Person
    var greeting: String? {
        didSet { self.greetingDidChange?(self) }
    }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
    required init(person: Person) { self.person = person }
    func showGreeting() {
        self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
    }
}

class GreetingViewController: UIViewController {
    var viewModel: GreetingViewModelProtocol! {
        didSet {
            self.viewModel.greetingDidChange = { [unowned self] vm in
                self.greetingLabel.text = vm.greeting
            }
        }
    }
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside)
    }
    // layout code goes here
}

// Assembling of MVVM
let model = Person(firstName: "David", lastName: "Blaine")
let viewModel = GreetingViewModel(person: model)
let view = GreetingViewController()
view.viewModel = viewModel

MVVM provides strong testability of the ViewModel and reduces UI‑controller code when bindings are used.

VIPER

VIPER splits responsibilities into View, Interactor, Presenter, Entity, and Router, offering the finest granularity but at the cost of many small classes.

import UIKit

struct Person { // Entity
    let firstName: String
    let lastName: String
}

struct GreetingData { // Transport data
    let greeting: String
    let subject: String
}

protocol GreetingProvider { func provideGreetingData() }
protocol GreetingOutput: class { func receiveGreetingData(greetingData: GreetingData) }
protocol GreetingViewEventHandler { func didTapShowGreetingButton() }
protocol GreetingView: class { func setGreeting(greeting: String) }

class GreetingInteractor: GreetingProvider {
    weak var output: GreetingOutput!
    func provideGreetingData() {
        let person = Person(firstName: "David", lastName: "Blaine")
        let data = GreetingData(greeting: "Hello", subject: "\(person.firstName) \(person.lastName)")
        self.output.receiveGreetingData(data)
    }
}

class GreetingPresenter: GreetingOutput, GreetingViewEventHandler {
    weak var view: GreetingView!
    var greetingProvider: GreetingProvider!
    func didTapShowGreetingButton() { self.greetingProvider.provideGreetingData() }
    func receiveGreetingData(greetingData: GreetingData) {
        let greeting = greetingData.greeting + " " + greetingData.subject
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController: UIViewController, GreetingView {
    var eventHandler: GreetingViewEventHandler!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    func didTapButton(button: UIButton) { self.eventHandler.didTapShowGreetingButton() }
    func setGreeting(greeting: String) { self.greetingLabel.text = greeting }
    // layout code goes here
}

// Assembling of VIPER (without Router)
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.view = view
presenter.greetingProvider = interactor
interactor.output = presenter

VIPER excels at task distribution and testability, but the sheer number of components can increase maintenance overhead.

Summary

All four patterns have trade‑offs; MVC is quick to start but scales poorly, MVP improves testability with moderate boilerplate, MVVM adds data binding for cleaner UI code, and VIPER offers the most modular design at the cost of complexity. Choose the pattern that matches your project’s size and future maintenance needs.

Source: CocoaChina (original article: http://www.cocoachina.com/ios/20160108/14916.html)
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Mobile DevelopmentarchitectureiOSMVCMVVMMVPViper
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.