当前位置: 首页 > article >正文

iOS开发设计模式篇第二篇MVVM设计模式

目录

一、什么是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 是一种分层架构,将应用分为以下三个部分:

  1. Model(数据层):负责存储和处理数据。它可以是业务逻辑对象、数据库模型或网络响应对象。

  2. View(视图层):负责显示 UI,并响应用户交互。

  3. ViewModel(视图模型层):负责将 Model 转化为 View 可以使用的数据,同时接收 View 的操作并更新 Model。

        通过 ViewModel,View 和 Model 之间不再直接通信,从而降低了耦合性。

二、MVVM 的主要特点

        数据绑定:通过绑定机制实现 View 和 ViewModel 的双向通信。

        单一职责:Model、View 和 ViewModel 各自专注于自己的职责。

        易于测试:ViewModel 的逻辑独立于 UI,便于单元测试。

三、MVVM 的架构图

View <-----> ViewModel <-----> Model

  1. View:只关心如何展示数据,通常使用 UIKit 或 SwiftUI 构建。
  2. ViewModel:充当中间层,负责从 Model 获取数据,并将其转换为适合展示的数据格式。
  3. 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 开发中,特别是复杂的应用程序中,它能显著提高代码的可维护性和扩展性。通过正确地分层设计,你可以更轻松地应对不断变化的需求。


http://www.kler.cn/a/523431.html

相关文章:

  • ios swift画中画技术尝试
  • cursor软件的chat和composer分别是什么
  • 获取snmp oid的小方法1(随手记)
  • unity实现回旋镖函数
  • java基础-容器
  • OpenCV:开运算
  • ReactNative react-devtools 夜神模拟器连调
  • 【云安全】云原生-K8S-搭建/安装/部署
  • 【LeetCode: 941. 有效的山脉数组 + 双指针】
  • 学习数据结构(1)时间复杂度
  • CRM 微服务
  • LeetCode 119. 杨辉三角 II
  • PyCharm中Python打包工具不见了的解决方案
  • parametric_vector = linspace(0, 1, num_points);详细解释
  • 【Python百日进阶-Web开发-FastAPI】Day813 - FastAPI 响应模型
  • Linux——网络基础(1)
  • 基于SpringBoot的阳光幼儿园管理系统
  • MongoDB平替数据库对比
  • DeepSeek:突破传统的AI算法与下载排行分析
  • 输出国际象棋棋盘
  • GBase 8a 9.5.3.27 DBlink配置---源端GBase
  • Xposed-Hook
  • 每日一题 430. 扁平化多级双向链表
  • Nginx 性能优化技巧与实践(二)
  • GORM 支持的数据库解析
  • 力扣 【99. 恢复二叉搜索树】Java题解(二叉树的 Morris 遍历)