Middle
What is the difference between MVM and MVP?
sobes.tech AI
Answer from AI
MVM (Model-View-ViewModel) and MVP (Model-View-Presenter) are user interface design patterns.
Differences:
- Binding:
- MVM uses two-way data binding between View and ViewModel. Changes in ViewModel automatically reflect in View, and vice versa.
- MVP typically uses one-way binding: the Presenter updates the View, and the View notifies the Presenter of events.
- Responsibility of View:
- In MVM, the View is "passive"; its main task is to display data from the ViewModel and send user actions back to the ViewModel. The presentation logic is entirely in the ViewModel.
- In MVP, the View is also "passive," but the Presenter fully manages its updates and reactions to events. The View informs the Presenter of events, and the Presenter decides how to react and update the View.
- Testability:
- The ViewModel in MVM is easy to test as it does not depend on UIKit/AppKit.
- The Presenter in MVP is also well testable, but its dependency on an abstract View interface may require mocking.
- Dependencies:
- MVM: View depends on ViewModel, ViewModel depends on Model.
- MVP: View depends on Presenter, Presenter depends on Model. View and Presenter are connected via interfaces.
- Coordination:
- In MVM, the View calls commands or methods in the ViewModel in response to user actions.
- In MVP, the View calls methods in the Presenter through an interface, and the Presenter calls methods to update the View also via an interface.
Example of MVM (schematic):
// Model
struct User {
let name: String
}
// ViewModel
class UserViewModel { // ObservableObject in SwiftUI or KVO/Reactive in UIKit
@Published var userName: String = "" // Or Observable in Reactive frameworks
private var user: User?
func loadUser() {
// Load data from Model
user = User(name: "John Doe")
userName = user?.name ?? ""
}
func changeName(newName: String) {
// Handle logic and update Model (if necessary)
// user?.name = newName // If Model is mutable
userName = newName
}
}
// View (SwiftUI)
struct UserView: View {
@StateObject var viewModel = UserViewModel()
var body: some View {
VStack {
Text(viewModel.userName) // Binding
Button("Load User") {
viewModel.loadUser() // Action binding
}
TextField("New Name", text: $viewModel.userName) // Two-way binding
}
.onAppear {
viewModel.loadUser() // Example call on appear
}
}
}
Example of MVP (schematic):
// Model
struct User {
let name: String
}
// View Protocol
protocol UserViewProtocol: AnyObject {
func display(userName: String)
func showLoadingIndicator()
func hideLoadingIndicator()
}
// Presenter
class UserPresenter {
private weak var view: UserViewProtocol?
private var user: User?
init(view: UserViewProtocol) {
self.view = view
}
func viewDidLoad() {
view?.showLoadingIndicator()
// Asynchronous data load from Model
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.user = User(name: "Jane Doe")
self.view?.hideLoadingIndicator()
self.view?.display(userName: self.user?.name ?? "")
}
}
func didTapChangeNameButton(newName: String) {
// Handle logic and update Model (if necessary)
// user?.name = newName // If Model is mutable
view?.display(userName: newName)
}
}
// View (UIKit)
class UserViewController: UIViewController, UserViewProtocol {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
@IBOutlet weak var nameTextField: UITextField!
private var presenter: UserPresenter!
override func viewDidLoad() {
super.viewDidLoad()
presenter = UserPresenter(view: self)
presenter.viewDidLoad()
}
// MARK: - UserViewProtocol
func display(userName: String) {
nameLabel.text = userName
}
func showLoadingIndicator() {
loadingIndicator.startAnimating()
}
func hideLoadingIndicator() {
loadingIndicator.stopAnimating()
}
// Trigger presenter actions from UI events
@IBAction func changeNameButtonTapped(_ sender: UIButton) {
if let newName = nameTextField.text {
presenter.didTapChangeNameButton(newName: newName)
}
}
}