iOS应用架构设计:MVC、MVVM、VIPER对比|一个深夜刷题工程师的血泪总结

极客生活家
2025-12-17 15:23
阅读 216

上周五晚上 11 点,我还在公司对着 Xcode 调一个诡异的内存泄漏问题——又是 View Controller 持有太多业务逻辑,导致 dealloc 根本不触发。那一刻我真的想把 MacBook 直接砸了,但一想到下周还要交 LeetCode 周赛打卡(跳槽人设不能崩),只能默默泡了杯速溶咖啡,继续肝。

我是小红书推荐算法组干了两年的 iOS 工程师(没错,算法岗也要写 App!)。虽然主业是搞用户增长模型,但因为团队人少活多,经常被拉去救火客户端项目。最近准备跳槽,除了狂刷《剑指 Offer》和《算法导论》这种硬核书籍,也得回头补一补基础架构知识——毕竟大厂面试官最爱问“你们 App 用啥架构”。

今天就来盘一盘 iOS 三大经典架构:MVC、MVVM、VIPER。这不是教科书式的理论堆砌,而是我在真实项目里踩过坑、熬过夜、被产品经理追着改需求后总结的最佳实践


为啥要关心架构?因为烂代码会杀人

去年双11期间,我们团队紧急上线一个“种草心愿单”功能。当时为了赶 deadline,直接在 ViewController 里塞了网络请求、数据解析、埋点上报、甚至部分推荐逻辑……结果上线第三天就 crash 率飙升,Crashlytics 里全是 EXC_BAD_ACCESS

测试同学疯狂 at 我:“你这个 VC 有 800 行,还持有 URLSession、Timer、还有 JSBridge 回调???”
我:……(内心 OS:这锅我不背,是产品说“先上线再说”)

痛定思痛,我们决定重构。但选哪种架构?MVC 太老,MVVM 听起来高大上,VIPER 又感觉过度设计。于是花了两周时间,在三个分支分别实现同一功能,横向对比效果。


MVC:苹果亲儿子,但容易变“Massive VC”

MVC 是 Apple 官方推荐的架构,Xcode 模板默认就是它。简单说就是:

  • Model:数据层(比如 User、Product)
  • View:UI 层(UILabel、UIButton)
  • Controller:协调者(UIViewController)

听起来很美好,但现实是:90% 的逻辑都塞进了 ViewController。为什么?因为 View 不能直接和 Model 通信,所有交互必须经过 VC。久而久之,VC 就成了“上帝类”。

// 典型的 Massive VC 写法(别学!)
class ProductDetailViewController: UIViewController {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var priceLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        fetchProduct { [weak self] product in
            self?.nameLabel.text = product.name
            self?.priceLabel.text = "$\(product.price)"
            Analytics.logEvent("view_product", params: ["id": product.id]) // 埋点?
            if product.isRecommendable {
                self?.showRecommendBanner() // 推荐逻辑?
            }
        }
    }
    
    func fetchProduct(completion: @escaping (Product) -> Void) {
        // 网络请求 + JSON 解析(可能还用了 SwiftyJSON)
    }
}

问题在哪?

  • 测试困难:VC 依赖 UIKit,单元测试几乎不可能
  • 复用性差:换个页面就得复制粘贴 500 行
  • 内存泄漏高发区:闭包没 weak self、delegate 没置 nil……

📌 我的建议:MVC 可以用,但必须严格约束 VC 职责——只做 UI 绑定和生命周期管理,其他统统抽出去!


MVVM:解耦神器,但别被 RxSwift 带偏了

被领导逼着学 MVVM 那会儿,我差点以为自己要转行做前端——满屏的 Observable、bind(to:)、DisposeBag……好家伙,这不就是用 Swift 写 React 吗?

但冷静下来发现,核心思想其实很简单

  • View:纯 UI,被动接收数据
  • ViewModel:处理业务逻辑,暴露可绑定的数据(比如 Published 变量)
  • Model:不变

关键在于:View 和 ViewModel 通过数据绑定通信,而不是直接调用方法

// SwiftUI + Combine 版 MVVM(更符合 Apple 生态)
class ProductDetailViewModel: ObservableObject {
    @Published var productName = ""
    @Published var productPrice = ""
    @Published var shouldShowRecommend = false
    
    private let productService: ProductService
    
    init(productService: ProductService) {
        self.productService = productService
    }
    
    func loadProduct(id: String) {
        productService.fetch(id: id) { [weak self] result in
            switch result {
            case .success(let product):
                self?.productName = product.name
                self?.productPrice = "$\(product.price)"
                self?.shouldShowRecommend = product.isRecommendable
                Analytics.logEvent("view_product", params: ["id": product.id])
            case .failure(let error):
                print("Load failed: \(error)")
            }
        }
    }
}

// View 层(SwiftUI)
struct ProductDetailView: View {
    @ObservedObject var viewModel: ProductDetailViewModel
    
    var body: some View {
        VStack {
            Text(viewModel.productName)
            Text(viewModel.productPrice)
            if viewModel.shouldShowRecommend {
                RecommendBanner()
            }
        }
        .onAppear {
            viewModel.loadProduct(id: "123")
        }
    }
}

优点很明显

  • ViewModel 无 UIKit 依赖,单元测试轻松写
  • 逻辑集中,复用方便(比如 H5 页面也能用同一个 VM)
  • SwiftUI 天然契合 MVVM(Apple 自己推的组合拳)

但注意两个坑

  1. 别为了“响应式”而响应式!简单场景用 @Published + assign 就够了,别一上来就 RxSwift,增加维护成本。
  2. JSBridge 调用要小心:我们有个需求是从 WebView 调原生方法,一开始把 JS 回调写在 ViewModel 里,结果内存炸了——WebView 持有 VM 引用,VM 又持有 WebView,循环引用!最后拆成独立的 WebBridgeManager 才解决。

📌 我的建议:MVVM + SwiftUI 是当前最优解,尤其适合新项目。老项目迁移可逐步替换,别追求一步到位。


VIPER:架构洁癖患者的终极选择?

VIPER 听起来像特工代号,实际是:

  • View:只负责 UI
  • Interactor:业务逻辑
  • Presenter:View 和 Interactor 的粘合剂
  • Entity:数据模型
  • Router:页面跳转

每个模块严格单向依赖,测试覆盖率能冲到 90%+。但代价是:文件数量爆炸。一个简单页面要建 5 个文件!

// 仅展示 Presenter 和 Interactor 的交互
protocol ProductDetailPresenterOutput: AnyObject {
    func displayProductName(_ name: String)
}

class ProductDetailInteractor {
    weak var output: ProductDetailInteractorOutput?
    private let service: ProductService
    
    func fetchProduct(id: String) {
        service.fetch(id: id) { [weak self] result in
            if case .success(let product) = result {
                self?.output?.display(product: product) // 回调给 Presenter
            }
        }
    }
}

class ProductDetailPresenter {
    weak var viewController: ProductDetailDisplayLogic?
    private let interactor: ProductDetailInteractor
    
    func viewDidLoad() {
        interactor.fetchProduct(id: "123")
    }
    
    func display(product: Product) {
        viewController?.displayProductName(product.name)
        // ...其他 UI 更新
    }
}

适合谁用?

  • 金融、医疗等对稳定性要求极高的 App
  • 团队有专职架构师,能 hold 住复杂度
  • 你享受写 Protocol 和 delegate 的快感(不是)

不适合谁?

  • 小团队(我们组 3 个 iOS,根本没人 review 这么多文件)
  • 快速迭代项目(产品经理每天改需求,VIPER 改到哭)
  • 我这种边刷题边干活的打工人(时间不够啊!)

横向对比:别光听概念,看真实数据

维度 MVC MVVM VIPER
上手难度 ⭐⭐ ⭐⭐⭐⭐
代码量 少(但集中在 VC) 极多
测试友好度 极好
SwiftUI 兼容 一般 完美 需改造
适合团队规模 1~2 人 2~10 人 10+ 人
我的推荐指数 🌕(慎用) 🌕🌕🌕🌕🌕 🌕🌕

最后说点掏心窝子的话

作为既要搞算法又要救火客户端的苦命人,我的结论很务实:

  • 新项目:直接 MVVM + SwiftUI,拥抱 Apple 生态,审核也更稳(App Store 最近严查滥用 UIKit)
  • 老项目:别盲目重构!先把 VC 里的网络、埋点、业务逻辑抽成独立 Manager,再逐步引入 ViewModel
  • 别炫技:我见过有人在简单设置页用 VIPER,结果新人入职一周还没搞懂怎么改个按钮文案……

对了,最近在啃《iOS 应用安全与逆向工程》这本书(跳槽面安全岗备选),发现架构混乱的 App 更容易被 hook——因为逻辑都暴露在 VC 里。所以啊,好的架构不仅是工程问题,更是安全防线

写完这篇,LeetCode 还剩两题……程序员的命也是命,但 deadline 不等人。共勉!

(PS:如果你也在深夜刷题/改 Bug,评论区抱团取暖吧!)

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝