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

news2025/1/26 14:22:57

目录

一、什么是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.coloradmin.cn/o/2281715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kafak 单例生产者实现-C#操作

前面写了一篇入门操作的文章,因为工作需要,简单修改了下如何实现单例生产者。 Kafka入门-C#操作_c# kafka-CSDN博客文章浏览阅读1.6k次,点赞20次,收藏9次。2).报错:“kafka.zookeeper.ZooKeeperClientTimeoutException: Timed out waiting for connection while in state…

JAVA与数据结构-线性表

目录 一.线性表的概念 二.线性表的关系及分类 三.数组与顺序表 四.链表 1.静态链表(链表的的数组底层实现&#xff09; 2.循环链表 3.双向链表 五.栈 1.栈的概念 2.栈的底层实现 3.共享空间栈 4.逆波兰表达式&#xff08;后缀表达式&#xff09; 5.栈与递归 六.…

2024.1.22 安全周报

政策/标准/指南最新动态 01 工信部印发《关于加强互联网数据中心客户数据安全保护的通知》 原文: https://www.secrss.com/articles/74673 互联网数据中心作为新一代信息基础设施&#xff0c;承载着千行百业的海量客户数据&#xff0c;是关系国民经济命脉的重要战略资源。…

WPS数据分析000005

目录 一、数据录入技巧 二、一维表 三、填充柄 向下自动填充 自动填充选项 日期填充 星期自定义 自定义序列 1-10000序列 四、智能填充 五、数据有效性 出错警告 输入信息 下拉列表 六、记录单 七、导入数据 ​编辑 八、查找录入 会员功能 Xlookup函数 VL…

如何使用 Node.js 构建一个简单的 API?

如何使用 Node.js 构建一个简单的 API&#xff1f; 在现代 Web 开发中&#xff0c;构建高效的 API 是连接前端与后端的核心任务之一。本文将向您展示如何使用 Node.js 构建一个简单的 API&#xff0c;同时通过示例说明如何测试 API。 步骤一&#xff1a;安装 Node.js 和创建项…

StarRocks强大的实时数据分析

代码仓库&#xff1a;https://github.com/StarRocks/starrocks?tabreadme-ov-file StarRocks | A High-Performance Analytical Database 快速开始&#xff1a;StarRocks | StarRocks StarRocks 是一款高性能分析型数据仓库&#xff0c;使用向量化、MPP 架构、CBO、智能物化…

详解Redis的Zset类型及相关命令

目录 Zset简介 ZADD ZCARD ZCOUNT ZRANGE ZREVRANGE ZRANGEBYSCORE ZPOPMAX BZPOPMAX ZPOPMIN BZPOPMIN ZRANK ZREVRANK ZSCORE ZREM ZREMRANGEBYRANK ZREMRANGEBYSCORE ZINCRBY ZINTERSTORE 内部编码 应用场景 Zset简介 有序集合相对于字符串、列表、哈希…

Android实训十 数据存储和访问

实训10 数据存储和访问 一、【实训目的】 1、 SharedPreferences存储数据; 2、 借助Java的I/O体系实现文件的存储&#xff0c; 3、使用Android内置的轻量级数据库SQLite存储数据; 二、【实训内容】 1、实现下图所示的界面&#xff0c;实现以下功能&#xff1a; 1&#x…

在Unity中使用大模型进行离线语音识别

文章目录 1、Vosk下载下载vosk-untiy-asr下载模型在项目中使用语音转文字音频转文字2、whisper下载下载unity项目下载模型在unity中使用1、Vosk 下载 下载vosk-untiy-asr Github链接:https://github.com/alphacep/vosk-unity-asr 进不去Github的可以用网盘 夸克网盘链接:h…

华为支付接入规范

为了确保用户获得良好的支付体验&#xff0c;Payment Kit制定了相关接入设计规范&#xff0c;请开发者遵照执行&#xff0c;具体要求&#xff08;非强制性&#xff09;如下&#xff1a; 一、支付方式呈现 涉及支付公司名称&#xff0c;请统一使用&#xff1a;花瓣支付&#xff…

数据结构——实验八·学生管理系统

嗨~~欢迎来到Tubishu的博客&#x1f338;如果你也是一名在校大学生&#xff0c;正在寻找各种编程资源&#xff0c;那么你就来对地方啦&#x1f31f; Tubishu是一名计算机本科生&#xff0c;会不定期整理和分享学习中的优质资源&#xff0c;希望能为你的编程之路添砖加瓦⭐&…

【C++篇】红黑树封装 实现map和set

目录 前言&#xff1a; 一&#xff0c;库中map和set的大致结构 二&#xff0c;模拟实现 2.1&#xff0c;大致框架 2.2&#xff0c;复用红黑树实现insert接口 2.3&#xff0c;迭代器iterator的实现 operator()的实现&#xff1a; operator--()的实现&#xff1a; 对inser…

解决CentOS9系统下Zabbix 7.2图形中文字符乱码问题

操作系统&#xff1a;CentOS 9 Zabbix版本&#xff1a;Zabbix7.2 问题描述&#xff1a;主机图形中文字符乱码 解决方案&#xff1a; # 安装字体配置和中文语言包 sudo yum install -y fontconfig langpacks-zh_CN.noarch # 检查是否已有中文字体&#xff1a; fc-list :lan…

统计文本文件中单词频率的 Swift 与 Bash 实现详解

网罗开发 &#xff08;小红书、快手、视频号同名&#xff09; 大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等…

计算机网络 (57)改进“尽最大努力交付”的服务

前言 计算机网络中的“尽最大努力交付”服务是网络层的一种数据传输方式。这种服务的特点是网络层只负责尽力将数据报从源端传输到目的端&#xff0c;而不保证数据传输的可靠性。 一、标记与分类 为数据分组打上标记&#xff1a; 给不同性质的分组打上不同的标记&#x…

联想电脑怎么设置u盘启动_联想电脑设置u盘启动方法(支持新旧机型)

有很多网友问联想电脑怎么设置u盘启动&#xff0c;联想电脑设置u盘启动的方法有两种&#xff0c;一是通过bios进行设置。二是通过快捷方式启动进入u盘启动。但需要注意有两种引导模式是&#xff0c;一种是uefi引导&#xff0c;一种是传统的leacy引导&#xff0c;所以需要注意制…

Springboot3 自动装配流程与核心文件:imports文件

注&#xff1a;本文以spring-boot v3.4.1源码为基础&#xff0c;梳理spring-boot应用启动流程、分析自动装配的原理 如果对spring-boot2自动装配有兴趣&#xff0c;可以看看我另一篇文章&#xff1a; Springboot2 自动装配之spring-autoconfigure-metadata.properties和spring…

SET alter system reload

目录标题 alter system 只是 写 auto 文件SET & alter system1. **会话级别参数&#xff08;Session-level parameters&#xff09;**2. **系统级别参数&#xff08;System-level parameters&#xff09;**3. **某些特定的超级用户参数**4. **修改时生效的参数**总结&#…

RPC是什么?和HTTP区别?

RPC 是什么&#xff1f;HTTP 是什么&#xff1f; 作为一个程序员&#xff0c;假设我们需要从A电脑的进程发送一段数据到B电脑的进程&#xff0c;我们一般会在代码中使用 Socket 进行编程。 此时&#xff0c;可选性一般就是 TCP 和 UDP 二选一&#xff0c;由于 TCP 可靠、UDP 不…

Y1打卡学习笔记

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客>- **&#x1f356;原作者&#xff1a;K同学啊** yolov5学习 下载源码运行命令查看结果视频检测个人总结 下载源码 地址&#xff1a;https://github.com/ultralytics/yolov5打开cmd后输入&#xff1…