如何快速掌握iOS三大主流架构设计模式
大家好,我是你们的学长,也是一名在高校带iOS实训课的讲师。作为一名211计算机专业的研究生,我在平时的学习和教学中发现,很多刚接触iOS开发的新人,代码写着写着就全堆在ViewController里,最后连自己都不敢改,一改就崩。
我当初学的时候也经历过这个“至暗时刻”,写出的代码被导师批得体无完肤。后来我痛定思痛,系统研究了iOS的架构设计,才算是真正入了门。写这篇教程,就是想用最通俗的语言,结合我这几年的实战和教学经验,帮零基础的新人彻底搞懂MVC、MVVM和VIPER这三种主流架构。
环境准备与辅助工具
在开始写代码前,我们需要准备好开发环境。
- 硬件与系统:一台Mac电脑,系统建议升级到最新的macOS。
- 开发工具:去App Store下载最新的Xcode。这是苹果官方的IDE,包含了编译器、模拟器和调试工具。
- 语言基础:本教程使用Swift语言,请确保你对Swift的基础语法(如类、结构体、协议、闭包)有初步了解。
学长的高效学习小贴士: 现在AI技术这么发达,大家一定要学会利用工具。我当初学的时候只能啃厚厚的英文文档,现在你们幸福多了。我强烈推荐大家使用 Anthropic 开发的Claude大模型来辅助学习,遇到不懂的架构概念直接问它,它能给你举出非常生动的例子。此外,B站和YouTube上有很多优质的 AI视频 教程,可以跟着视频里的UP主一起敲代码。甚至在实际工程中,我们有时候会用 Javascript 写一些自动化脚本,配合Sourcery等工具来批量生成VIPER架构中繁琐的模板代码,极大地提高了开发效率。
核心概念通俗解析
架构设计听起来很高大上,其实说白了就是“如何给代码分工”。我们来看看这三位“老熟人”。
1. MVC:经典的“大包大揽”
MVC全称是Model-View-Controller。
- Model(模型):负责管理数据,比如从服务器获取用户信息。
- View(视图):负责界面展示,比如按钮、文本框。
- Controller(控制器):负责连接Model和View,处理用户的交互逻辑。
痛点:在iOS中,由于View和Controller耦合太深,导致Controller既要处理UI逻辑,又要处理网络请求和数据转换,最后变成了传说中的“Massive View Controller”(巨型视图控制器),几千行代码全塞在一个文件里。
2. MVVM:引入“翻译官”
MVVM全称是Model-View-ViewModel。它是MVC的进化版。
- View和Model:职责和MVC一样。
- ViewModel(视图模型):这是核心!它是一个“翻译官”,负责把Model里的数据转换成View能直接使用的格式,并且处理所有的业务逻辑。
优势:Controller变瘦了,它只负责把ViewModel的数据“绑定”到View上。ViewModel不依赖具体的UI,因此非常容易写单元测试。
3. VIPER:极致的“大公司分工”
VIPER不是那个杀毒软件,它是View、Interactor、Presenter、Entity、Routing的缩写。
- View:纯展示,接收用户事件并告诉Presenter。
- Interactor(交互器):包含核心业务逻辑和网络请求。
- Presenter(展示器):接收View的事件,调用Interactor获取数据,然后格式化数据交给View展示。
- Entity(实体):纯数据模型。
- Routing(路由):负责页面跳转。
优势:极度的单一职责,每个文件都很小,非常适合大型团队协作。 劣势:代码量爆炸,写一个简单的页面可能要建五六个文件。
实战项目:用户资料展示
光说不练假把式。我们以一个“获取并展示用户个人资料”的功能为例,看看三种架构在代码层面到底有什么区别。
实战一:MVC模式下的用户资料
在MVC中,ViewController是个“全能王”。
import UIKit
// Model: 用户数据模型
struct User {
let name: String
let age: Int
}
// MVC中的ViewController
class UserMVCViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var ageLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// 1. 发起网络请求 (业务逻辑)
fetchUserData()
}
func fetchUserData() {
// 模拟网络请求
DispatchQueue.global().async {
// 假设这里从服务器拿到了数据
let user = User(name: "张三", age: 25)
// 2. 更新UI (必须在主线程)
DispatchQueue.main.async {
self.nameLabel.text = "姓名:\(user.name)"
self.ageLabel.text = "年龄:\(user.age)"
}
}
}
}
学长点评:代码看起来很直观,但如果这个页面有几十个Label,还要处理各种复杂的表单验证,这个VC的代码量会迅速膨胀到无法维护。
实战二:MVVM模式下的用户资料
在MVVM中,我们把网络请求和数据格式化抽离到ViewModel中。为了对零基础友好,这里使用闭包回调来实现数据绑定(实际项目中推荐使用Combine框架)。
import UIKit
// Model
struct User {
let name: String
let age: Int
}
// ViewModel: 负责业务逻辑和数据转换
class UserViewModel {
// 定义闭包,当数据更新时通知View
var onDataUpdated: ((String, String) -> Void)?
func fetchUser() {
// 模拟网络请求
DispatchQueue.global().async {
let user = User(name: "李四", age: 28)
// 在ViewModel中完成数据格式化
let nameText = "姓名:\(user.name)"
let ageText = "年龄:\(user.age)"
DispatchQueue.main.async {
// 通过闭包把格式化好的数据传给View
self.onDataUpdated?(nameText, ageText)
}
}
}
}
// View (ViewController变瘦了)
class UserMVVMViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var ageLabel: UILabel!
// 持有ViewModel
let viewModel = UserViewModel()
override func viewDidLoad() {
super.viewDidLoad()
// 监听ViewModel的数据变化
viewModel.onDataUpdated = { [weak self] name, age in
self?.nameLabel.text = name
self?.ageLabel.text = age
}
// 触发数据获取
viewModel.fetchUser()
}
}
学长点评:ViewController现在只负责“监听”和“展示”,具体的脏活累活都交给了ViewModel。这样即使UI大改,只要数据格式不变,ViewModel的代码一行都不用动。
实战三:VIPER模式下的用户资料
VIPER的代码量较大,我用文字流程图结合核心代码来展示。
VIPER交互流程:
- View 触发加载事件 -> 通知 Presenter
- Presenter 调用 Interactor 获取数据
- Interactor 请求网络,拿到 Model(Entity) 返回给 Presenter
- Presenter 将 Model 转换为 View 需要的格式,通知 View 更新UI
// 1. 定义各模块之间的通信协议 (面向协议编程)
protocol UserVIPERViewProtocol: AnyObject {
func showUserData(name: String, age: String)
}
protocol UserVIPERPresenterProtocol: AnyObject {
func viewDidLoad()
}
protocol UserVIPERInteractorProtocol: AnyObject {
func fetchUser(completion: @escaping (User) -> Void)
}
// 2. Entity (纯数据)
struct User {
let name: String
let age: Int
}
// 3. Interactor (干脏活的)
class UserInteractor: UserVIPERInteractorProtocol {
func fetchUser(completion: @escaping (User) -> Void) {
// 模拟网络请求
DispatchQueue.global().async {
let user = User(name: "王五", age: 30)
DispatchQueue.main.async {
completion(user)
}
}
}
}
// 4. Presenter (承上启下的枢纽)
class UserPresenter: UserVIPERPresenterProtocol {
weak var view: UserVIPERViewProtocol?
var interactor: UserVIPERInteractorProtocol?
func viewDidLoad() {
interactor?.fetchUser { [weak self] user in
// 数据格式化
let nameText = "姓名:\(user.name)"
let ageText = "年龄:\(user.age)"
// 通知View更新
self?.view?.showUserData(name: nameText, age: ageText)
}
}
}
// 5. View (纯粹的展示)
class UserVIPERViewController: UIViewController, UserVIPERViewProtocol {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var ageLabel: UILabel!
var presenter: UserVIPERPresenterProtocol?
override func viewDidLoad() {
super.viewDidLoad()
presenter?.viewDidLoad()
}
// 实现协议方法,更新UI
func showUserData(name: String, age: String) {
nameLabel.text = name
ageLabel.text = age
}
}
学长点评:看到了吧?VIPER把职责切分得极其细致。虽然看起来文件变多了,但每个类的代码都不超过50行。在几十人的大团队里,这种架构能避免多人修改同一个文件导致的代码冲突。
架构对比与选型指南
为了让大家更直观地理解,我整理了一个对比表格:
| 对比维度 | MVC | MVVM | VIPER |
|---|---|---|---|
| 代码复杂度 | 低 | 中 | 高 |
| 文件数量 | 少 (1-2个) | 中 (2-3个) | 多 (5个以上) |
| 可测试性 | 差 (UI与逻辑耦合) | 好 (ViewModel易测试) | 极好 (完全解耦) |
| 学习曲线 | 平缓 | 中等 | 陡峭 |
| 适用场景 | 小型项目、快速原型 | 中大型项目、主流选择 | 超大型项目、复杂业务 |
| 团队协作 | 易冲突 | 较好 | 极好 |
新手常见问题解答 (FAQ)
Q1:我是新手,第一个项目应该用哪种架构? 学长答:毫无疑问,先用MVC。不要一上来就搞VIPER,你会被繁琐的协议和文件绕晕的。先用MVC把功能跑通,当你发现ViewController代码超过500行,改一个Bug引出三个新Bug的时候,再尝试用MVVM去重构它。
Q2:MVVM一定要配合RxSwift或者Combine这种响应式框架吗? 学长答:不一定。对于零基础或者中小型项目,使用简单的闭包回调(如我上面实战代码所示)或者Delegate(代理)模式来实现数据绑定就足够了。响应式框架学习成本较高,建议把MVVM的思想掌握后,再去进阶学习Combine。
Q3:公司老项目全是MVC,老板让我重构,我该怎么下手? 学长答:千万不要试图一次性重构整个项目!采用“绞杀者模式”,在新开发的页面或者需要大改的页面使用MVVM,老页面保持MVC不动。慢慢让MVVM的页面占据主导,最终自然过渡。
学习建议与避坑指南
作为过来人,我给初学者的最后几点建议:
- 不要为了架构而架构:架构的目的是为了解决问题,而不是为了炫技。如果一个简单的设置页面,你用VIPER写了5个文件,那就是过度设计。
- 多读优秀开源代码:推荐去GitHub上看看像
CleanArchitectureRxSwift这样的优秀项目,看看大神们是如何组织代码的。 - 画架构图:在写代码前,先用纸笔或者工具(如Draw.io)把模块之间的调用关系画出来。理清了数据流向,代码写起来会如丝般顺滑。
- 重视单元测试:MVVM和VIPER最大的优势之一就是好写测试。尝试为你的ViewModel写几个单元测试,这会极大地提升你的代码质量。
iOS开发是一场马拉松,架构设计是其中的重要补给站。希望这篇教程能帮你建立起正确的架构思维。如果在实践中遇到任何问题,欢迎在评论区留言,学长看到都会回复的。祝大家代码无Bug,早日成为iOS大佬!

评论 0