iOS 工程

[iOS架构 #1] iOS MVC模式与Massive View Controller产生的真正原因

进行iOS开发时,几乎每个人都听过这个笑话。

4 分钟阅读
[iOS架构 #1] iOS MVC模式与Massive View Controller产生的真正原因 封面图

进行iOS开发时,几乎每个人都听过这个笑话。

“MVC不是Model-View-Controller,而是Massive View Controller的缩写。”

这是句玩笑话,但很有道理。明明是Apple官方推荐的架构,为什么照着做,视图控制器最终会变成几千行的怪物?

作为iOS架构系列的第一篇,今天我们来梳理MVC原本的样子,以及为什么iOS无法按那个样子工作

先说结论,问题不在MVC概念本身,而在于UIViewController横跨View和Controller边界的结构


MVC原本是什么样的结构?

MVC是1979年诞生于Smalltalk的古老模式。原始形式明确划分了三种职责。

  • Model:数据和业务逻辑
  • View:屏幕显示
  • Controller:接收用户输入并传递给Model

在原始MVC中,View直接观察Model。Model发生变化时,View会自动更新。

不过Apple的MVC有所不同。它让View和Model彼此完全不知情,并改为由Controller在中间负责所有协调。这是为了提高复用性的合理选择,却也为Controller承担过多工作埋下了伏笔。


UIViewController光是名字就违规了

Apple MVC中的Controller在iOS里由UIViewController实现。但请再看看这个类名:View + Controller。名字本身就合并了两种职责。

实际上,UIViewController负责以下所有工作。

  • 管理viewDidLoadviewWillAppear视图生命周期
  • 处理屏幕旋转、布局更新等与视图紧密相关的事件
  • 实现UITableViewDataSourceUITableViewDelegate视图协议

到这里还都是视图相关工作。问题是,像“网络请求写在哪里?”“页面跳转呢?”“数据格式化呢?”这些问题都没有明确答案。它们既不是Model,也不是View,最后全都进了视图控制器。

光是名字就是View + Controller,默认就得一个人包办所有事情。
光是名字就是View + Controller,默认就得一个人包办所有事情。

臃肿视图控制器的典型样子

常见的视图控制器结构,用代码概括如下。

final class ProfileViewController: UIViewController {
    // 1. 视图属性
    private let tableView = UITableView()

    // 2. 状态(实际上是 Model 缓存)
    private var user: User?
    private var posts: [Post] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        setupLayout()          // 3. 布局代码
        fetchProfile()         // 4. 网络请求
    }

    private func fetchProfile() {
        URLSession.shared.dataTask(...) { ... }  // 5. 解析、错误处理
    }

    @objc private func editTapped() {
        // 6. 甚至直接负责页面跳转
        navigationController?.pushViewController(EditViewController(), animated: true)
    }
}

布局、状态管理、网络、解析和页面跳转全都在一个文件里。再加上delegate实现,很快就会超过一千行。

这种结构真正的代价不是代码行数,而是无法进行测试。即使要验证“user为nil时隐藏编辑按钮”这样的简单逻辑,也必须完整启动UIViewController并模拟它的生命周期。


那么,MVC应该放弃吗?

不一定。对于小型页面,MVC仍然是最快、最简单的选择。Apple的框架本身就是以MVC为前提设计的,强行套用其他结构反而可能增加摩擦。

关键在于,即使使用MVC,也要有意识地拆出可以从视图控制器中移除的职责

  • 网络与数据逻辑 → 独立的服务对象
  • 页面跳转 → Coordinator等专用对象
  • 单元格构建、格式化 → 专用类型

接下来,你会想把“创建屏幕要显示的状态”的逻辑也分离出去。答案就是下一篇要介绍的MVVM。我们还会梳理为什么需要ViewModel,以及没有绑定时为什么只能完成一半。

第一步,就是有意识地分离可以拆出的职责。
第一步,就是有意识地分离可以拆出的职责。

总结

  • MVC本身没有错。问题在于iOS的结构特性:UIViewController同时承担View和Controller的角色。
  • 既不是Model也不是View的代码无处安放,最终全都堆积到视图控制器中,这就是Massive View Controller的本质。
  • 对于小型页面,MVC仍然有效。但网络、页面跳转和格式化必须有意识地分离。
  • 如果还要分离创建视图状态的逻辑,就需要MVVM。下一篇介绍。

延伸阅读