目录
一、什么是MVVM
二、MVVM 的主要特点
三、MVVM 的架构图
四、MVVM 与其他模式的对比
五、如何在iOS中实现MVVM
1.Model
2.ViewModel
3.View (ViewController)
4.双向绑定
5.文中完整的代码地址
六、MVVM 的优缺点
1.优点
2.缺点
七、MVVM 的应用场景
八、结语
在iOS开发中,设计模式对于提升代码的可维护性、可读性和扩展性有着重要作用。其中,MVVM (Model-View-ViewModel) 是一种流行的架构模式,它通过引入 ViewModel 层解决了 View 和 Model 耦合过高的问题。本文将详细介绍MVVM的基本概念、实现原理以及在iOS中的实际应用。
一、什么是MVVM
MVVM 是一种分层架构,将应用分为以下三个部分:
-
Model(数据层):负责存储和处理数据。它可以是业务逻辑对象、数据库模型或网络响应对象。
-
View(视图层):负责显示 UI,并响应用户交互。
-
ViewModel(视图模型层):负责将 Model 转化为 View 可以使用的数据,同时接收 View 的操作并更新 Model。
通过 ViewModel,View 和 Model 之间不再直接通信,从而降低了耦合性。
二、MVVM 的主要特点
数据绑定:通过绑定机制实现 View 和 ViewModel 的双向通信。
单一职责:Model、View 和 ViewModel 各自专注于自己的职责。
易于测试:ViewModel 的逻辑独立于 UI,便于单元测试。
三、MVVM 的架构图
View <-----> ViewModel <-----> Model
- View:只关心如何展示数据,通常使用 UIKit 或 SwiftUI 构建。
- ViewModel:充当中间层,负责从 Model 获取数据,并将其转换为适合展示的数据格式。
- Model:负责处理底层数据逻辑,如网络请求或数据库操作。
四、MVVM 与其他模式的对比
特性 | MVC | MVVM |
数据绑定 | 无 | 有 |
View 和 Model 耦合 | 高耦合 | 松耦合 |
测试 | 较难测试 | 易于测试 |
学习成本 | 低 | 中等 |
在小型项目中,MVC 可能更加轻便;但在大型项目中,MVVM 可以显著提升代码的可维护性。
五、如何在iOS中实现MVVM
以下我们通过一个简单的用户信息显示例子来演示如何使用 MVVM。
最终的效果图如下:
图1.mvvm的例子
1.Model
在本文的例子中,Model表示示例中使用到的数据模型,即用户名和年龄。
import Foundation
// MARK: - Model
class UserModel: NSObject {
@objc dynamic var name: String
@objc dynamic var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
2.ViewModel
ViewModel中充当View和Model的中间层。它有两个作用。
1.获取Model中的数据并把它转成View中要使用的数据格式。
在本文的代码中,我们提供一个初始化的方法,把Model中的数据转成要展示的name和age属性,同时我们通过KVO的方式监听UserModel中的变化,实时获取变化之后的最新值。
第二个提供一个方法接收View的操作并且更新Model。
import Foundation
// MARK: - ViewModel
class UserInfoViewModel: NSObject {
@objc dynamic var name: String
@objc dynamic var age: Int
private var user: UserModel
init(user: UserModel) {
self.user = user
self.name = user.name
self.age = user.age
super.init()
// 观察 Model 的变化并同步到 ViewModel
self.user.addObserver(self, forKeyPath: #keyPath(UserModel.name), options: [.new], context: nil)
self.user.addObserver(self, forKeyPath: #keyPath(UserModel.age), options: [.new], context: nil)
}
deinit {
// 移除观察者
user.removeObserver(self, forKeyPath: #keyPath(UserModel.name))
user.removeObserver(self, forKeyPath: #keyPath(UserModel.age))
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath else { return }
switch keyPath {
case #keyPath(UserModel.name):
if let newName = change?[.newKey] as? String {
name = newName
}
case #keyPath(UserModel.age):
if let newAge = change?[.newKey] as? Int {
age = newAge
}
default:
break
}
}
func updateUser(name: String, age: Int) {
user.name = name
user.age = age
}
}
3.View (ViewController)
这里View的指的是View和UIViewController。我们把UIView和UIViewController都看做事MVVM设计模式中的View。它的作用是和ViewModel通信。
import UIKit
import IFLYCommonKit
import Foundation
class UserInfoViewController: IFLYCommonBaseVC {
private var viewModel: UserInfoViewModel!
private let nameLabel = UILabel()
private let ageLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
let user = UserModel(name: "John", age: 25)
viewModel = UserInfoViewModel(user: user)
bindViewModel()
}
private func setupUI() {
view.addSubview(nameLabel)
view.addSubview(ageLabel)
nameLabel.frame = CGRect(x: 20, y: 100, width: 200, height: 30)
ageLabel.frame = CGRect(x: 20, y: 150, width: 200, height: 30)
}
private func bindViewModel() {
nameLabel.text = viewModel.displayName
ageLabel.text = viewModel.displayAge
}
}
4.双向绑定
如果需要双向绑定,可以引入第三方库如 Combine 或 RxSwift。
这里使用KVO实现。
import UIKit
import IFLYCommonKit
import Foundation
class UserInfoViewController: IFLYCommonBaseVC {
private var viewModel = UserInfoViewModel(user: UserModel(name: "unknown", age: 0))
// UI 元素
private let nameLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 18)
label.textColor = .black
return label
}()
private let ageLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.textColor = .darkGray
return label
}()
private let updateButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Update Info", for: .normal)
button.backgroundColor = .systemBlue
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 8
return button
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupBindings()
}
// MARK: - Setup UI
private func setupUI() {
title = "MVVM设计模式"
view.backgroundColor = .white
view.addSubview(nameLabel)
view.addSubview(ageLabel)
view.addSubview(updateButton)
// SnapKit 布局
nameLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(20)
}
ageLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(nameLabel.snp.bottom).offset(10)
}
updateButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(ageLabel.snp.bottom).offset(20)
make.width.equalTo(150)
make.height.equalTo(40)
}
updateButton.addTarget(self, action: #selector(updateUserInfo), for: .touchUpInside)
}
// MARK: - Bindings
private func setupBindings() {
// KVO 绑定
viewModel.addObserver(self, forKeyPath: "name", options: [.new, .initial], context: nil)
viewModel.addObserver(self, forKeyPath: "age", options: [.new, .initial], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "name", let newName = change?[.newKey] as? String {
nameLabel.text = "Name: \(newName)"
} else if keyPath == "age", let newAge = change?[.newKey] as? Int {
ageLabel.text = "Age: \(newAge)"
}
}
deinit {
viewModel.removeObserver(self, forKeyPath: "name")
viewModel.removeObserver(self, forKeyPath: "age")
}
// MARK: - Actions
@objc private func updateUserInfo() {
// 随机更新用户信息
let randomNames = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace"]
let newName = randomNames.randomElement() ?? "Unknown"
let newAge = Int.random(in: 18...60)
viewModel.updateUser(name: newName, age: newAge)
}
}
5.文中完整的代码地址
文章篇幅有限,懒人直接点击这里获取。
六、MVVM 的优缺点
1.优点
1. 低耦合:View 和 Model 解耦,便于扩展。
2. 易测试:ViewModel 不依赖 UI,测试更容易。
3. 代码复用性强:ViewModel 可以在多个 View 中复用。
2.缺点
1. 初始学习成本较高。
2. 对于小型项目可能显得过于复杂。
七、MVVM 的应用场景
1. 中大型项目:适合复杂的数据流和界面逻辑。
2. 需要数据绑定的项目:如表单输入、列表数据展示。
3. 跨平台项目:如同时支持 iOS 和 macOS 的项目,ViewModel 可以很容易地复用。
八、结语
MVVM 是一种强大的设计模式,在 iOS 开发中,特别是复杂的应用程序中,它能显著提高代码的可维护性和扩展性。通过正确地分层设计,你可以更轻松地应对不断变化的需求。