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.
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 = modelWhile 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 = presenterMVP 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 = viewModelMVVM 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 = presenterVIPER 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)
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
