8. MVC在移动应用开发中的应用:以iOS的MVC模式为例

移动端开发,说白了就是跟屏幕尺寸、触摸交互、还有碎片化的生命周期打交道。我做了这么多年iOS开发,发现很多新手一上来就堆代码,Controller里塞了几千行,最后自己都看不懂。嗯,这其实就是没用好MVC。

今天咱们就拿iOS来聊聊,MVC在移动端到底该怎么落地。我个人的经验是,iOS的MVC虽然被吐槽“太重”,但只要你把边界划清楚,它依然是中小型项目最稳的架构。

8.1 iOS中的MVC角色划分

在iOS里,MVC的三个角色跟经典定义略有不同。苹果官方文档其实写得很清楚,但很多人没仔细看。

角色 iOS对应组件 核心职责
Model NSObject / Codable 结构体 数据存储、业务逻辑、网络请求结果
View UIView / Storyboard / XIB 界面展示、用户交互响应
Controller UIViewController 协调Model与View、处理生命周期、事件转发

你可能会问:那UIViewController到底算不算Controller?算,但也不完全是。我在项目中遇到过一种情况:把网络请求、数据解析、UI刷新全塞进ViewController,结果一个文件三千行。这其实已经违背了MVC的初衷——Controller应该只做“协调”,而不是“包办”。

8.2 一个典型的iOS MVC示例

咱们来看一个用户列表的案例。假设我们要从网络获取用户数据,然后展示在表格里。

Model层

struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

class UserService {
    func fetchUsers(completion: @escaping ([User]) -> Void) {
        // 模拟网络请求
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            let users = [
                User(id: 1, name: "张三", email: "zhangsan@example.com"),
                User(id: 2, name: "李四", email: "lisi@example.com")
            ]
            DispatchQueue.main.async {
                completion(users)
            }
        }
    }
}

这里我把数据模型和网络服务都归为Model层。有人可能会把网络请求放到Controller里,我个人习惯是坚决不——Controller一旦掺和网络逻辑,测试就变得非常痛苦。

View层

class UserCell: UITableViewCell {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
    
    func configure(with user: User) {
        nameLabel.text = user.name
        emailLabel.text = user.email
    }
}

View只负责展示,不关心数据从哪来。这个Cell拿到User对象,直接往Label上填就行。我曾经见过有人直接在Cell里写网络请求,那画面太美我不敢看。

Controller层

class UserListViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    private var users: [User] = []
    private let userService = UserService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        loadUsers()
    }
    
    private func loadUsers() {
        userService.fetchUsers { [weak self] users in
            self?.users = users
            self?.tableView.reloadData()
        }
    }
}

extension UserListViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserCell
        cell.configure(with: users[indexPath.row])
        return cell
    }
}

Controller在这里只做了三件事:触发加载、接收数据、刷新UI。它不直接操作网络,也不直接操作Cell的Label。说白了,它就是个“中间人”。

核心原则:Controller只做“转发”和“协调”,不做“实现”。如果你发现Controller里出现了大量的if-else或者数据处理逻辑,赶紧抽到Model层去。

8.3 MVC在移动端的常见陷阱

我刚开始用MVC做iOS开发时,踩过不少坑。这里列几个最常见的,你对照看看自己有没有中招。

  • Massive ViewController——这是iOS MVC被黑得最惨的地方。Controller里塞了太多东西,导致难以维护。解决办法:把网络请求抽到Service,把数据转换抽到ViewModel(虽然这有点MVVM的味道,但实用主义至上)。
  • View直接持有Model——有些新手直接在UIView里写一个var user: User,然后在View里处理业务逻辑。这会导致View和Model耦合,换数据源时得改View代码。正确做法是View只暴露配置方法,由Controller来调用。
  • 忽略Model的独立性——Model应该是一个纯粹的Swift结构体或类,不依赖UIKit。我见过有人把UIImage写到Model里,结果单元测试跑不起来。记住:Model只关心数据,不关心展示。
避坑指南:我曾经在一个项目里把CoreData的NSManagedObject直接当Model用,结果View层直接操作数据库对象,导致数据不一致。后来我加了一层DTO(Data Transfer Object),问题才解决。记住:Model不等于数据库实体

8.4 MVC在iOS中的通信机制

MVC三个角色之间怎么通信?这是很多人的盲区。我画了一张图,帮你理清关系。

Model 数据 + 业务逻辑 UserService / User View 界面展示 UserCell / UITableView Controller 协调者 UserListViewController 数据回调 刷新UI 用户事件 请求数据 通信方向:View → Controller → Model → Controller → View

从图上你能看到,View和Model永远不直接通信。所有的交互都经过Controller。这其实是个好设计——哪天你想把UITableView换成UICollectionView,只需要改View层,Model完全不用动。

小技巧:在iOS中,View向Controller传递事件通常用Target-Action(按钮点击)或Delegate(表格行选中)。我个人建议尽量用Delegate,因为Target-Action容易把逻辑散落在各处。Delegate把事件集中到Controller的一个扩展里,代码更清晰。

8.5 实际项目中的MVC优化

说实话,纯MVC在大型项目里确实会有点吃力。我一般会在MVC基础上做两点优化:

  1. 引入Service层——把网络请求、数据库操作、文件读写都封装到Service里。Controller只调用Service,不关心具体实现。这样单元测试时,Mock一个Service就行。
  2. 使用Notification或KVO做松耦合——当Model发生变化时,通过Notification广播给多个Controller。比如用户登录状态变化,多个页面都需要刷新,用Notification比层层传递方便得多。

但要注意,不要滥用Notification。我曾经在一个项目里用了二十多个Notification,结果调试时根本不知道谁发了谁收了。后来我改用RxSwift,但那是另一个话题了。

8.6 总结

iOS的MVC模式,说白了就是把界面、数据、协调三者拆开。你想想看,如果你把这三样混在一起,改UI时可能影响数据,改数据时可能搞崩界面。拆开之后,每个部分都可以独立测试、独立修改。

我个人的经验是:MVC不是银弹,但它是最容易上手、最容易理解的架构。对于中小型iOS项目,MVC完全够用。如果你发现Controller开始膨胀了,别急着换架构,先试试把业务逻辑抽到Model层,把网络请求抽到Service层。很多时候,问题就解决了。

嗯,今天就聊到这儿。记住:架构是为业务服务的,别为了用架构而用架构。