iOS开发架构——MVC、MVP和MVVM对比

news2025/5/12 18:50:24

文章目录

  • 前言
  • MVC(Model - View - Controller)
  • MVP(Model - View - Presenter)
  • MVVM(Model - View - ViewModel)


前言

在 iOS 开发中,MVC、MVVM、和 MVP 是常见的三种架构模式,它们主要目的是解耦视图与业务逻辑,提高代码复用性和可维护性。下面我将通过一个简单的示例「展示用户名」来解释它们的区别,并配上对应代码。

假设场景:

  • 有一个 User 模型,包含昵称 name
  • UI 包含一个 UILabel 显示名字,一个 UIButton 模拟点击“加载用户”
  • 点击按钮 → 加载用户数据 → 显示用户昵称

MVC(Model - View - Controller)

特点:

  • Controller 是桥梁:连接 Model 和 View
  • View 很「傻」,Controller 很「胖」(逻辑全堆里面)
// Model
struct User {
    var name: String
}

// ViewController 充当 Controller 和 View 的职责
class MVCViewController: UIViewController {
    let nameLabel = UILabel()
    let loadButton = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()

        setupUI()
    }

    func setupUI() {
        nameLabel.frame = CGRect(x: 50, y: 100, width: 200, height: 30)
        loadButton.frame = CGRect(x: 50, y: 150, width: 100, height: 40)
        loadButton.setTitle("加载用户", for: .normal)

        loadButton.addTarget(self, action: #selector(loadUser), for: .touchUpInside)

        view.addSubview(nameLabel)
        view.addSubview(loadButton)
    }

    // 模拟点击:在 Controller 中处理逻辑
    @objc func loadUser() {
        let user = User(name: "小明") // 模拟从后端获取
        nameLabel.text = user.name    // 更新 UI
    }
}

在 iOS 的 MVC 架构中,ViewController 集中包含了 View 和 Controller 的逻辑,这是由 Apple 官方 UIKit 框架的设计风格所造成的,而不是 MVC 理论的本意。

UIKit 组件(如 UIViewController)本身就是既负责控制逻辑(Controller),又默认持有和管理 UI(View)的类。比如:

class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // 控制逻辑(Controller)
        // 创建和管理 UI(View)
        let label = UILabel()
        label.text = "Hello"
        self.view.addSubview(label)
    }
}

UIViewController.view 就是一个默认的视图容器。所以,View 和 Controller 就混在一起用了。

所以很多人吐槽 iOS MVC 是 “Massive View Controller”,代码都堆在 VC 里,不利于维护。

MVP(Model - View - Presenter)

在 iOS 的 MVC 中,ViewController 既负责 UI,又负责业务逻辑。随着功能增加,代码会变得:

  • 臃肿(Massive ViewController)
  • 难以测试
  • 难以复用

MVP 将业务逻辑(如获取数据、处理点击事件)抽到 Presenter 中,ViewController 只关心 UI。

特点:

  • Presenter 处理业务逻辑(可以单元测试),不依赖 UIKit
  • View 是一个协议,由 ViewController 实现(View 使用协议抽象,可以 mock View,可以单元测试)
  • 更适合做单元测试
// Model
struct User {
    var name: String
}

// View 协议:只关心显示逻辑
protocol UserView: AnyObject {
    func displayUserName(_ name: String)
}

// Presenter:负责处理业务逻辑
class UserPresenter {
    weak var view: UserView?

    init(view: UserView) {
        self.view = view
    }

    func fetchUser() {
        let user = User(name: "小红") // 模拟数据获取
        view?.displayUserName(user.name)
    }
}

// ViewController 实现 View 协议
class MVPViewController: UIViewController, UserView {
    let nameLabel = UILabel()
    let loadButton = UIButton(type: .system)
    var presenter: UserPresenter!

    override func viewDidLoad() {
        super.viewDidLoad()

        presenter = UserPresenter(view: self)
        setupUI()
    }

    func setupUI() {
        nameLabel.frame = CGRect(x: 50, y: 100, width: 200, height: 30)
        loadButton.frame = CGRect(x: 50, y: 150, width: 100, height: 40)
        loadButton.setTitle("加载用户", for: .normal)

        loadButton.addTarget(self, action: #selector(loadUserTapped), for: .touchUpInside)

        view.addSubview(nameLabel)
        view.addSubview(loadButton)
    }

    @objc func loadUserTapped() {
        presenter.fetchUser()
    }

    func displayUserName(_ name: String) {
        nameLabel.text = name
    }
}

虽然 MVP 已经将业务逻辑抽离到了 Presenter,但:

  1. View 仍需手动更新 UI,Presenter 获取数据后,需要调用 View 协议来“告诉”它更新 UI:
// ViewController 实现这些更新方法,很容易随着页面变复杂而变臃肿。
view?.showUserName(user.name)
  1. 通信是被动式的,如果状态很多(例如 name、age、头像等),Presenter 需要逐个通知,View 也要逐个处理。

MVVM(Model - View - ViewModel)

特点:

  • 引入绑定机制(数据驱动 UI)
MVVM 中,ViewModel 暴露可观察属性(如 Swift 的 @PublishedRxSwiftObservableUI 层通过绑定,一旦数据变化就自动刷新,不需要手动调用更新方法。
  • ViewController 更轻量,不再关心如何显示,而是“绑定好”UI 到 ViewModel
viewModel.$name.sink { self.nameLabel.text = $0 }
  • 状态管理更统一,ViewModel 通常以“状态集合”的方式存在,能更好地组织
@Published var isLoading: Bool
@Published var name: String
@Published var errorMessage: String?
import Combine

// Model
struct User {
    var name: String
}

// ViewModel:处理数据逻辑和 UI 映射
class UserViewModel: ObservableObject {
    @Published var username: String = ""

    func loadUser() {
        let user = User(name: "小花")
        username = user.name // 触发 UI 更新
    }
}

// ViewController
class MVVMViewController: UIViewController {
    let nameLabel = UILabel()
    let loadButton = UIButton(type: .system)
    var viewModel = UserViewModel()
    var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()

        setupUI()
        bindViewModel()
    }

    func setupUI() {
        nameLabel.frame = CGRect(x: 50, y: 100, width: 200, height: 30)
        loadButton.frame = CGRect(x: 50, y: 150, width: 100, height: 40)
        loadButton.setTitle("加载用户", for: .normal)
        loadButton.addTarget(self, action: #selector(loadUserTapped), for: .touchUpInside)

        view.addSubview(nameLabel)
        view.addSubview(loadButton)
    }

    func bindViewModel() {
        viewModel.$username
            .receive(on: RunLoop.main)
            .sink { [weak self] name in
                self?.nameLabel.text = name
            }
            .store(in: &cancellables)
    }

    @objc func loadUserTapped() {
        viewModel.loadUser()
    }
}

分析:

  • 数据绑定调试困难(数据变化自动触发 UI,数据流是隐式的,调试时很难知道“谁更新了谁”)
  • 不适合所有页面(小页面 + 简单逻辑,用 MVC 或 MVP 更高效)
  • 双向绑定滥用风险(状态混乱或循环更新)
  • ViewModel 可能过于庞大(Massive ViewModel)

ViewModel 承担了很多职责,如果不合理拆分,容易臃肿,等同于从 ViewController 搬家而已。

  • 接收用户输入
  • 做业务逻辑
  • 暴露 UI 状态
  • 响应用户行为

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2374130.html

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

相关文章

MySQL索引原理以及SQL优化(二)

目录 1. 索引与约束 1.1 索引是什么 1.2 索引的目的 1.3 索引分类 1.3.1 数据结构 1.3.2 物理存储 1.3.3 列属性 1.3.4 列的个数 1.4 主键的选择 1.5 索引使用场景 1.6 索引的底层实现 1.6.1 索引存储 1.6.2 页 1.6.3 B 树 1.6.4 B 树层高问题 1.6.5 自增 id 1.7 innod…

MATLAB中矩阵和数组的区别

文章目录 前言环境配置1. 数据结构本质2. 运算规则&#xff08;1&#xff09;基本运算&#xff08;2&#xff09;特殊运算 3. 函数与操作4. 高维支持5. 创建方式 前言 在 MATLAB 中&#xff0c;矩阵&#xff08;Matrix&#xff09; 和 数组&#xff08;Array&#xff09; 的概…

Desfire Ev1\Ev2\Ev3卡DES\3K3DES\AES加解密读写C#示例源码

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.1d292c1bYhsS9c&ftt&id917152255720 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using S…

MySQL核心内容【完结】

MySQL核心内容 文章目录 MySQL核心内容1.MySQL核心内容目录2.MySQL知识面扩展3.MySQL安装4.MySQL配置目录介绍Mysql配置远程ip连接 5.MySQL基础1.MySQL数据类型1.数值类型2.字符串类型3.日期和时间类型4.enum和set 2.MySQL运算符1.算数运算符2.逻辑运算符3.比较运算符 3.MySQL完…

C++类和对象进阶 —— 与数据结构的结合

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 […

Django之账号登录及权限管理

账号登录及权限管理 目录 1.登录功能 2.退出登录 3.权限管理 4.代码展示合集 这篇文章, 会讲到如何实现账号登录。账号就是我们上一篇文章写的账号管理功能, 就使用那里面已经创建好的账号。这一次登录, 我们分为三种角色, 分别是员工, 领导, 管理员。不同的角色, 登录进去…

EXCEL中嵌入其他表格等文件

在EXCEL中嵌入其他表格 先放链接&#xff1a;https://jingyan.baidu.com/article/295430f11708c34d7e00509a.html 步骤如下&#xff1a; 1、打开一个需要嵌入新表格的excel表。 2、切换至“插入”菜单中&#xff0c;单击选择“对象”。 3、如下图所示&#xff0c;会弹出“对象…

21. LangChain金融领域:合同审查与风险预警自动化

引言&#xff1a;当AI成为24小时不眠的法律顾问 2025年某商业银行的智能合同系统&#xff0c;将百万级合同审查时间从平均3周缩短至9分钟&#xff0c;风险条款识别准确率达98.7%。本文将基于LangChain的金融法律框架&#xff0c;详解如何构建合规、精准、可追溯的智能风控体系…

Springboot使用事件流调用大模型接口

什么是事件流 事件流&#xff08;Event Stream&#xff09; 是一种处理和传递事件的方式&#xff0c;通常用于系统中的异步消息传递或实时数据流。在事件驱动架构&#xff08;Event-Driven Architecture&#xff09;中&#xff0c;事件流扮演着至关重要的角色。 事件流的概念…

计算机网络--2

TCP三次握手 TCP连接为什么需要三次握手 1. 由于网络情况复杂,可能会出现丢包现象,如果第二次握手的时候服务器就认为这个端口可用,然后一直开启,但是如果客户端未收到服务器发送的回复,那么就会重新发送请求,服务器就会重新开启一个端口连接,这样就会浪费一个端口。 三…

尤雨溪宣布:Vue 生态正式引入 AI

在前端开发领域,Vue 框架一直以其易用性和灵活性受到广大开发者的喜爱。 而如今,Vue 生态在人工智能(AI)领域的应用上又迈出了重要的一步。 尤雨溪近日宣布,Vue、Vite 和 Rolldown 的文档网站均已添加了llms.txt文件,这一举措旨在让大型语言模型(LLM)更方便地理解这些…

蓝桥杯第十六届c组c++题目及个人理解

本篇文章只是部分题目的理解&#xff0c;代码和思路仅供参考&#xff0c;切勿当成正确答案&#xff0c;欢迎各位小伙伴在评论区与博主交流&#xff01; 题目&#xff1a;2025 题目解析 核心提取 要求的数中至少有1个0、2个2、1个5 代码展示 #include<iostream> #incl…

硬件工程师笔记——电子器件汇总大全

目录 1、电阻 工作原理 欧姆定律 电阻的物理本质 一、限制电流 二、分压作用 三、消耗电能&#xff08;将电能转化为热能&#xff09; 2、压敏电阻 伏安特性 1. 过压保护 2. 电压调节 3. 浪涌吸收 4. 消噪与消火花 5. 高频应用 3、电容 工作原理 &#xff08;…

微软推动智能体协同运作:支持 A2A、MCP 协议

今日凌晨&#xff0c;微软宣布 Azure AI Foundry 和 Microsoft Copilot Studio 两大开发平台支持最新 Agent 开发协议 A2A&#xff0c;并与谷歌合作开发扩大该协议&#xff0c;这一举措对智能体赛道意义重大。 现状与变革意义 当前智能体领域类似战国时代&#xff0c;各家技术…

Linxu实验五——NFS服务器

一.NFS服务器介绍 NFS服务器&#xff08;Network File System&#xff09;是一种基于网络的分布式文件系统协议&#xff0c;允许不同操作系统的主机通过网络共享文件和目录3。其核心作用在于实现跨平台的资源透明访问&#xff0c;例如在Linux和Unix系统之间共享静态数据&#…

20242817李臻《Linux⾼级编程实践》第9周

20242817李臻《Linux⾼级编程实践》第9周 一、AI对学习内容的总结 第十章 Linux下的数据库编程 10.1 MySQL数据库简介 MySQL概述&#xff1a;MySQL是一个开源的关系型数据库管理系统&#xff0c;最初由瑞典MySQL AB公司开发&#xff0c;后经SUN公司收购&#xff0c;现属于O…

开源分享:TTS-Web-Vue系列:SSML格式化功能与高级语音合成

&#x1f3af; 本文是TTS-Web-Vue系列的第十二篇文章&#xff0c;重点介绍项目新增的SSML格式化功能以及SSML在语音合成中的应用。通过自动格式化和实时预览&#xff0c;我们显著提升了SSML编辑体验&#xff0c;让用户能够更精确地控制语音合成的细节&#xff0c;实现更自然、更…

FAST-LIO笔记

1.FAST-LIO FAST-LIO 是一个计算效率高、鲁棒性强的激光-惯性里程计系统。该系统通过紧耦合的迭代扩展卡尔曼滤波器&#xff08;IEKF&#xff09;将激光雷达特征点与IMU数据进行融合&#xff0c;使其在快速运动、噪声较大或环境复杂、存在退化的情况下仍能实现稳定的导航。 1…

软考中级软件设计师——UML(统一建模语言)篇

UML的词汇表包含3种构造块:事物、关系和图。事物是对模型中最具有代表性的成分的抽象;关系把事物结合在一起;图聚集了相关的事物。 一、事物 UML 事物是模型中的基本元素&#xff0c;分为 结构事物、行为事物、分组事物、注释事物。 1. 结构事物 类&#xff08;Class&#x…

TSN网络与DIOS融合:破解煤矿井下电力系统越级跳闸难题

一、引言 1.1 研究背景与意义 在现代煤矿生产中&#xff0c;井下电力系统作为整个煤矿生产的动力核心&#xff0c;其重要性不言而喻。煤矿井下的各类机械设备&#xff0c;如采煤机、刮板输送机、通风机、排水泵等&#xff0c;都依赖稳定的电力供应才能正常运行。电力系统的稳定…