The differences between MVC and MVVM
This was inspired by a Stack Overflow post.
Model, Controller, View and ViewModel, are arbitrary labels used to describe different objects, in order to discuss and conceptualize their responsibilities and behaviors so that people can develop better and safer programs.
MVVM
- View ➡ ViewModel ➡ Model
- The view has a reference to the ViewModel but not vice versa.
- The ViewModel has a reference to the Model but not vice versa.
- The View has no reference to the Model and vice versa.
2. If you are using a controller, it can have a reference to Views and ViewModels, though a Controller is not always necessary as demonstrated in SwiftUI.
3. How can we reliably update the UI based on changes in the ViewModel?
Data Binding: we create listeners for ViewModel Properties.
class CustomView: UIView {
var viewModel = MyViewModel {
didSet {
self.color = viewModel.color
}
} convenience init(viewModel: MyViewModel) {
self.viewModel = viewModel
}
}
struct MyViewModel {
var viewColor: UIColor {
didSet {
colorChanged?() // This is where the binding magic happens.
}
}
var colorChanged: ((UIColor) -> Void)?
}class MyViewController: UIViewController { let myViewModel = MyViewModel(viewColor: .green)
let customView: CustomView! override func viewDidLoad() {
super.viewDidLoad()
}// This is where the binder is assigned. myViewModel.colorChanged = { [weak self] color in
print(“wow the color changed”)
} customView = CustomView(viewModel: myViewModel)
self.view = customView
}
}
MVC
- View ⇄ Controller ⇄Model
- The controller has reference/s to the View/s and the Model/s.
- The view/s hold reference/s to Controller (through Storyboard) but not the Model, so that user actions can send messages to the controller.
- The Model/s do/es not hold any reference to the View/s nor the Controller .except to controller in order to update it when messages are received from a remote or local server or model, via class delegation or delegation through closures.
2. Example
// MARK - The View/*The view, source code from Storyboarddestination="BYZ-38-t0r" is the view's reference to the Controller<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dmL-IS-1tM">
<rect key="frame" x="0.0" y="44" width="414" height="818"/>.
<color key="backgroundColor" systemColor="linkColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="Button"/>
<connections>
<action selector="buttonTapped:" destination="BYZ-38-t0r" eventType="touchUpInside" id="e99-ir-7C5"/>
</connections>
</button>
*/// MARK - The Controllerclass ViewController: UIViewController { @IBOutlet weak var button: UIButton! var newCar: Car {
return Car()
} override func viewDidLoad() {
super.viewDidLoad() // the encapsulated URL is considered a model as well, we can pass a closure to receive messages from the backend or use delegation. URL(string: .carURL)?.get { [weak self] json in
guard let car = Car(json) else { return
button.backgroundColor = car.color
button.setTitle(car.name, for: .normal)
}
} @IBAction func buttonTapped(_ sender: UIButton) {
sender.backgroundColor = newCar.color
sender.setTitle(newCar.name, for: .normal)
}
}// MARK - Modelstruct Car {
var name: String
var color: UIColor
init() {
self.color = .random
self.name = ["ford", "camero", "ferrari"].randomElement() ?? "Tesla"
} init?(_ json: [String: Any]) {
if let colorHex = json["color"] as? String,
let color = UIColor(hex: colorHex),
let name = json["name"] as? String {
self.name = name
self.color = color
}
}
}// MARK - Extended Modelinternal extension String {
var carURL: String {
/// interface
}
}extension URL {
typealias JSON = [String: Any]
typealias JSONAction = (JSON) -> Void
func get(_ jsonAction: JSONAction) {
/// interface
}
}extension UIColor {
class var random: UIColor {
return UIColor(
red: .random,
green: .random,
blue: .random,
alpha: 1.0
)
}
}extension CGFloat {
static var random: CGFloat {
return CGFloat(arc4random()) / CGFloat(UInt32.max)
}
}extension UIColor {
public convenience init?(hex: String) {
let r, g, b, a: CGFloat
if hex.hasPrefix("#") {
let start = hex.index(hex.startIndex, offsetBy: 1)
let hexColor = String(hex[start...])
if hexColor.count == 8 {
let scanner = Scanner(string: hexColor)
var hexNumber: UInt64 = 0
if scanner.scanHexInt64(&hexNumber) {
r = CGFloat((hexNumber & 0xff000000) >> 24) / 255
g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255
b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255
a = CGFloat(hexNumber & 0x000000ff) / 255
self.init(red: r, green: g, blue: b, alpha: a)
return
}
}
}
return nil
}
}
differences in setup
1. Business logic is held in the controller for MVC and the ViewModels for MVVM.
2. Events are passed directly from the View to the controller in MVC while events are passed from the View to the ViewModel to the Controller (if there is one) for MVVM.
Common features
1. Both MVVM and MVC do not allow the View to send messages directly to the Model/s.
2. Both have models.
3. Both have views.
Advantages of MVVM
1. Because the ViewModels hold business logic, they are smaller concrete objects making them easy to unit tests. On the other hand, in MVC, the business logic is in the ViewController. How can you trust that a unit test of a view controller is comprehensively safe without testing all the methods and listeners simultaneously? You can’t wholly trust the unit test results.
2. In MVVM, because business logic is siphoned out of the Controller into atomic ViewModel units, the size of the ViewController shrinks and this makes the ViewController code more legible.
Advantages of MVC
1. Providing business logic within the controller reduces the need for branching and therefore statements are more likely to run on the cache which is more performant over encapsulating business logic into ViewModels.
2. Providing business logic in one place can accelerate the development process for simple applications, where tests are not required. I don’t know when tests are not required.
3. Providing business logic in the ViewController is easier to think about for new developers.