惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
T
The Exploit Database - CXSecurity.com
阮一峰的网络日志
阮一峰的网络日志
F
Fox-IT International blog
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
I
Intezer
P
Privacy & Cybersecurity Law Blog
B
Blog RSS Feed
Latest news
Latest news
小众软件
小众软件
A
Arctic Wolf
Attack and Defense Labs
Attack and Defense Labs
L
LINUX DO - 热门话题
博客园 - 聂微东
B
Blog
T
Troy Hunt's Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
Malwarebytes
Malwarebytes
爱范儿
爱范儿
Recorded Future
Recorded Future
Apple Machine Learning Research
Apple Machine Learning Research
人人都是产品经理
人人都是产品经理
D
Docker
T
Threat Research - Cisco Blogs
MyScale Blog
MyScale Blog
Martin Fowler
Martin Fowler
E
Exploit-DB.com RSS Feed
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
PCI Perspectives
PCI Perspectives
Scott Helme
Scott Helme
N
Netflix TechBlog - Medium
博客园 - 三生石上(FineUI控件)
T
True Tiger Recordings
C
Check Point Blog
Microsoft Azure Blog
Microsoft Azure Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
K
Kaspersky official blog
Security Latest
Security Latest
The Hacker News
The Hacker News
Microsoft Security Blog
Microsoft Security Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Stack Overflow Blog
Stack Overflow Blog
S
Security @ Cisco Blogs
C
CXSECURITY Database RSS Feed - CXSecurity.com
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
M
Microsoft Research Blog - Microsoft Research

掘金

从“连接不上”到“交易成功”:我用 @solana/web3.js 在 React 中搞定 Solana 钱包交互的全过程 juejin.cn 海量人群包存储优化:基于 RoaringBitmap 交换格式与 Redis 分片 Bitmap 的实践 juejin.cn juejin.cn 鸿蒙项目首页启动链路与 ArkUI 架构学习总结 如何手写一个 AI Agent 工具调用循环(Tool Loop) Tauri 应用首次上架 App Store 被驳回了 3 次(iOS)和 12 轮(macOS)的经历 juejin.cn Flutter 桌面小组件开发 现代多模态大模型的核心基础:Unified Self-Attention juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn Transformer 原论文怎么训出来的:8 张 P100、12 小时、warmup 4000 步 Hermes 升级后,我的 Telegram 附件突然发不出来了 Transformer 中的前馈网络:那个看似平平无奇的两层 MLP,其实是「记忆」所在 AI Coding开始进入第四个时代,我还没上车呢! 【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (1)---基础 juejin.cn Vibe Coding 全栈实战:章鱼哥解题 01|搭好产品底座与登录链路 juejin.cn 我让 AI 加了一个开关,结果代码走了原本不该走的分支 Manim物理模拟:别自己写欧拉了! 我也该升级了,陪伴了我7年的博客 juejin.cn juejin.cn MCP 高德地图实战:当 AI 学会使用工具,一个协议如何重塑大模型的行动边界 用魔法打败魔法:我让AI替我去面试前端岗,AI面试官给我打了92分,还发了offer juejin.cn juejin.cn juejin.cn juejin.cn Android Input Spy Window Claude Code CLI 命令大全:60 个原生命令一次讲清 juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn 关于一个新手小白靠claude帮助下的全栈留言板项目开发 juejin.cn juejin.cn juejin.cn 从本地开发到生产部署:用 Docker Compose 跑通 NestJS、MySQL 与 Milvus AI应用开发七:可以替代 RAG 的技术 juejin.cn 小书匠:一款本地优先、去中心化的全能笔记软件 juejin.cn juejin.cn juejin.cn Shadow实战接入与生产落地:从零搭建到稳定运行 Shadow Transform:编译期的魔法——字节码替换实战 juejin.cn juejin.cn Hermes Agent:一个真正“会成长”的开源 AI Agent,正在改变 AI 自动化玩法 juejin.cn juejin.cn juejin.cn 残差连接:为什么深层网络必须留一条直路 juejin.cn FastAPI 从入门到实战:3 分钟构建高性能异步 API juejin.cn juejin.cn CryptoJS:数据安全的JavaScript加密利器 juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn ArkClaw AI 盯盘管家 —— 从手动口令到自动推送,4 套预置定时任务模版一键启用 juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn “杀!杀!杀!”、“我最讨厌事后道歉”——骂“杀哥”之前,谁还没当过情绪崩溃的人
iOS 架构模式全景图:MVC / MVVM / VIPER / Clean Architecture 选型指南
冰凌时空 · 2026-05-17 · via 掘金

专栏:iOS高级工程实践
编号:C01 · 系列第 1 篇
字数:约 6000 字
标签:iOS / 架构设计 / MVC / MVVM / VIPER / Clean Architecture / 架构选型


前言

「这个 App 架构选什么好?」

这是每个 iOS 工程师在项目初期都会面临的问题。网上有无数文章讲 MVC、MVVM、VIPER、Clean Architecture,但大多数只讲概念,不讲取舍。

今天,我们换一种方式:不只讲「是什么」,更讲「什么时候适合」,以及「换架构的代价是什么」。

读完这篇文章,你会得到:

  1. 决策树:根据项目特征快速匹配适合的架构
  2. 各架构的权衡分析:不只是优点,还有你必须付出的代价
  3. 渐进式迁移路径:如何从最简单的方式逐步演进到更复杂的架构

一、架构的本质是什么

在深入细节之前,我们需要先回答一个问题:架构到底是什么?

很多人把架构等同于「目录结构」或者「分层方式」,但这只是表面。

架构的本质是:系统中各组件之间**关系**的定义。

这些关系包括:

  • 数据流向:数据从哪里来,经过哪些处理,流向哪里
  • 依赖方向:谁依赖谁,谁不应该知道谁的存在
  • 边界划分:哪些代码属于同一模块,模块之间如何通信
  • 变更隔离:当需求变化时,需要改多少代码,改哪些文件

好的架构让变更容易,坏的架构让变更痛苦


二、MVC:Apple 的默认选择

2.1 标准 MVC 在 iOS 中的表现

Apple 的 MVC(Model-View-Controller)模式大概是这个样子:

┌─────────┐     ┌─────────────┐     ┌─────────┐
│  View   │◀───▶│ Controller  │◀───▶│  Model  │
│(UIView) │     │(UIViewController)│   │ (数据) │
└─────────┘     └─────────────┘     └─────────┘

Model:纯数据和业务逻辑。不持有任何 UI 组件,不处理 UI 事件。 View:UI 组件(UIView / SwiftUI View)。负责渲染和用户交互的捕获。 Controller:协调者。接收 View 的事件,更新 Model,再把 Model 的变化反映到 View 上。

2.2 代码示例

// Model
struct User {
    let id: UUID
    var name: String
    var email: String
}

// Controller - 典型的 iOS MVC
class UserViewController: UIViewController {
    // Model
    var user: User?

    // View - 以 IBOutlet 形式存在
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
    @IBOutlet weak var saveButton: UIButton!

    // 生命周期
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        setupActions()
    }

    // 更新 View
    func updateUI() {
        nameLabel.text = user?.name
        emailLabel.text = user?.email
    }

    // 处理用户操作
    @IBAction func saveButtonTapped(_ sender: UIButton) {
        // 获取输入
        user?.name = nameTextField.text ?? ""
        user?.email = emailTextField.text ?? ""

        // 业务逻辑(可能在这里,也可能放在 Model 里)
        UserService.shared.save(user!) { result in
            DispatchQueue.main.async {
                switch result {
                case .success:
                    self.showSuccessAlert()
                case .failure(let error):
                    self.showErrorAlert(error)
                }
            }
        }
    }
}

2.3 MVC 的优点

  1. 简单直接:概念少,新人容易理解
  2. Apple 原生支持:Xcode 的模板默认就是 MVC
  3. 适合小型项目:对于功能简单的 App,MVC 的效率最高
  4. 代码量少:不需要额外的抽象层

2.4 MVC 的问题:Massive View Controller

MVC 最大的问题在于 Controller 的边界模糊。当业务逻辑复杂时,所有东西都会堆积到 Controller 里:

// 真实的 MVC Controller 会长这样
class OrderViewController: UIViewController {
    // 10+ 个 IBOutlet
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var totalLabel: UILabel!
    @IBOutlet weak var addressTextField: UITextField!
    @IBOutlet weak var paymentButton: UIButton!
    @IBOutlet weak var couponTextField: UITextField!
    // ... 更多 IBOutlet

    // 5+ 个数据属性
    var order: Order?
    var cartItems: [CartItem] = []
    var coupon: Coupon?
    var selectedAddress: Address?
    var paymentMethod: PaymentMethod = .alipay

    // 3+ 个服务依赖
    let orderService = OrderService()
    let paymentService = PaymentService()
    let couponService = CouponService()
    let analyticsService = AnalyticsService()
    let notificationService = NotificationService()

    // 网络请求回调嵌套
    func loadOrderDetail() {
        orderService.fetchOrder(id: orderId) { [weak self] result in
            self?.handleOrderFetch(result)
            self?.loadCouponList()
            self?.loadAddressList()
            self?.trackPageView()
        }
    }

    func applyCoupon() {
        couponService.validate(code: couponTextField.text) { [weak self] result in
            guard let self else { return }
            switch result {
            case .success(let coupon):
                self.coupon = coupon
                self.updateTotalPrice()
                self.showCouponSuccess()
            case .failure(let error):
                self.showCouponError(error)
                self.resetCoupon()
            }
        }
    }

    // 20+ 个方法
    func updateTotalPrice() { ... }
    func validateForm() { ... }
    func submitOrder() { ... }
    func handlePaymentResult() { ... }
    func sendLocalNotification() { ... }
    func logAnalytics() { ... }
    // ... 更多方法
}

这就是传说中的 Massive View Controller。当 Controller 超过 500 行,测试几乎不可能,任何修改都可能影响全局。

2.5 什么时候用 MVC

适合不适合
小型 App(< 10 个页面)中大型 App
功能相对固定业务逻辑复杂多变
团队规模小(1-3 人)团队规模大,需要并行开发
快速原型验证需要长期维护
学习阶段生产环境核心业务

三、MVVM:Apple 推荐的现代方案

3.1 MVVM 的核心思想

MVVM(Model-View-ViewModel)通过引入 ViewModel 来解决 Controller 膨胀的问题:

┌─────────┐     ┌───────────────┐     ┌─────────┐
│  View   │◀───▶│   ViewModel   │◀───▶│  Model  │
│(UIView) │     │ (状态+逻辑)    │     │ (数据)  │
└─────────┘     └───────────────┘     └─────────┘
       │                                  ▲
       └───────── 数据绑定 ───────────────┘

关键创新:View 和 ViewModel 之间的数据绑定。View 观察 ViewModel 的状态变化,自动更新 UI,不需要手动写「更新 UI」的代码。

3.2 iOS 中的数据绑定方案

方案特点学习曲线性能
CombineApple 原生,响应式流中等
@Observable (iOS 17+)SwiftUI 原生,最简洁
RxSwift功能最强大,社区成熟
SwiftUI @StateObjectSwiftUI 内置

3.3 代码示例(Combine 版本)

import Combine

// ViewModel
class UserViewModel: ObservableObject {
    @Published var name: String = ""
    @Published var email: String = ""
    @Published var isLoading = false
    @Published var errorMessage: String?
    @Published var isSaveEnabled = false

    private var cancellables = Set<AnyCancellable>()
    private let userService: UserService

    init(userService: UserService = .shared) {
        self.userService = userService
        setupValidation()
    }

    // 业务逻辑在 ViewModel 中
    private func setupValidation() {
        // 组合多个验证条件
        Publishers.CombineLatest($name, $email)
            .map { name, email in
                !name.trimmingCharacters(in: .whitespaces).isEmpty &&
                email.contains("@") &&
                email.contains(".")
            }
            .assign(to: &$isSaveEnabled)
    }

    func save() async {
        isLoading = true
        errorMessage = nil

        do {
            let user = User(name: name, email: email)
            try await userService.save(user)
        } catch {
            errorMessage = error.localizedDescription
        }

        isLoading = false
    }
}

// View(UIKit + Combine)
class UserViewController: UIViewController {
    private let viewModel = UserViewModel()
    private var cancellables = Set<AnyCancellable>()

    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var saveButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupBindings()
        setupActions()
    }

    private func setupBindings() {
        // ViewModel → View:自动更新 UI
        viewModel.$isLoading
            .receive(on: DispatchQueue.main)
            .sink { [weak self] isLoading in
                self?.saveButton.isEnabled = !isLoading
                self?.isLoading ? self?.showLoading() : self?.hideLoading()
            }
            .store(in: &cancellables)

        viewModel.$errorMessage
            .compactMap { $0 }
            .receive(on: DispatchQueue.main)
            .sink { [weak self] message in
                self?.showError(message)
            }
            .store(in: &cancellables)

        viewModel.$isSaveEnabled
            .receive(on: DispatchQueue.main)
            .assign(to: \.isEnabled, on: saveButton)
            .store(in: &cancellables)
    }

    private func setupActions() {
        // View → ViewModel:用户输入驱动 ViewModel
        nameTextField.addTarget(self, action: #selector(nameChanged), for: .editingChanged)
        emailTextField.addTarget(self, action: #selector(emailChanged), for: .editingChanged)
        saveButton.addTarget(self, action: #selector(saveTapped), for: .touchUpInside)
    }

    @objc private func nameChanged(_ textField: UITextField) {
        viewModel.name = textField.text ?? ""
    }

    @objc private func emailChanged(_ textField: UITextField) {
        viewModel.email = textField.text ?? ""
    }

    @objc private func saveTapped() {
        Task {
            await viewModel.save()
        }
    }
}

3.4 SwiftUI 版本的 MVVM

import SwiftUI

// SwiftUI 天然 MVVM,数据绑定极简
struct UserView: View {
    @StateObject private var viewModel = UserViewModel()

    var body: some View {
        Form {
            TextField("Name", text: $viewModel.name)
            TextField("Email", text: $viewModel.email)

            Button("Save") {
                Task { await viewModel.save() }
            }
            .disabled(!viewModel.isSaveEnabled || viewModel.isLoading)

            if viewModel.isLoading {
                ProgressView()
            }

            if let error = viewModel.errorMessage {
                Text(error).foregroundColor(.red)
            }
        }
    }
}

3.5 MVVM 的优点

  1. 测试友好:ViewModel 不持有任何 UI 组件,可以直接测试
  2. Controller 瘦身:ViewController 从 500 行变成 50 行
  3. 数据绑定减少样板代码:不需要手动同步 UI 和数据
  4. 团队协作友好:View 和 ViewModel 可以并行开发

3.6 MVVM 的缺点

  1. 学习曲线:需要理解响应式编程或 Combine
  2. 过度工程风险:对于简单页面,MVVM 可能比 MVC 代码量更多
  3. ViewModel 可能膨胀:如果不注意,ViewModel 会变成新的 Massive ViewModel
  4. 数据绑定的隐式依赖:绑定链长时,调试困难

3.7 什么时候用 MVVM

适合不适合
中型 App(10-50 个页面)极简单的单页面 App
需要单元测试不需要测试的项目
团队有 Combine/RxSwift 经验团队完全没有响应式基础
需要数据绑定的复杂表单静态页面为主
SwiftUI 项目需要大量 UIKit 特定 API 的项目

四、VIPER:大型项目的选择

4.1 VIPER 的五个组件

VIPER 是 View / Interactor / Presenter / Entity / Router 的缩写,每个组件职责单一:

┌─────────────┐
│    View     │ 负责 UI 渲染和用户输入捕获
└──────┬──────┘
       │
┌──────▼──────┐
│  Presenter  │ 格式化数据,准备显示,处理用户手势
└──────┬──────┘
       │
┌──────▼──────┐
│ Interactor  │ 业务逻辑和数据处理(无 UI 知识)
└──────┬──────┘
       │
┌──────▼──────┐
│   Entity    │ 纯数据模型
└─────────────┘
       │
┌──────▼──────┐
│   Router    │ 导航和模块间跳转
└─────────────┘

4.2 代码示例

// Entity - 纯数据
struct UserEntity {
    let id: UUID
    let name: String
    let email: String
}

// Interactor - 业务逻辑
protocol UserDetailInteractorProtocol {
    func fetchUser(id: UUID) async throws -> UserEntity
    func updateUser(_ user: UserEntity) async throws
}

class UserDetailInteractor: UserDetailInteractorProtocol {
    private let repository: UserRepository

    func fetchUser(id: UUID) async throws -> UserEntity {
        // 纯业务逻辑,没有任何 UI 代码
        try await repository.getUser(id: id)
    }

    func updateUser(_ user: UserEntity) async throws {
        try await repository.updateUser(user)
    }
}

// Presenter - 格式化显示
protocol UserDetailPresenterProtocol {
    func presentUser(_ user: UserEntity)
    func presentError(_ error: Error)
    func presentSaveSuccess()
}

class UserDetailPresenter: UserDetailPresenterProtocol {
    weak var view: UserDetailViewProtocol?
    private let interactor: UserDetailInteractorProtocol
    private let router: UserDetailRouterProtocol

    func presentUser(_ user: UserEntity) {
        let viewModel = UserViewModel(
            name: user.name,
            email: user.email,
            formattedDate: formatDate(user.createdAt)
        )
        view?.displayUser(viewModel)
    }

    private func formatDate(_ date: Date) -> String {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        return formatter.string(from: date)
    }
}

// Router - 导航
protocol UserDetailRouterProtocol {
    func navigateToEditScreen(user: UserEntity)
    func dismiss()
}

class UserDetailRouter: UserDetailRouterProtocol {
    weak var viewController: UIViewController?

    func navigateToEditScreen(user: UserEntity) {
        let editVC = UserEditRouter.createModule(user: user)
        viewController?.navigationController?.pushViewController(editVC, animated: true)
    }
}

4.3 VIPER 的优点

  1. 职责极致单一:每个组件只做一件事
  2. 高度可测试:Interactor 可以独立于 UI 测试
  3. 适合大型团队:各组件可以并行开发
  4. 支持模块化:每个 VIPER 模块是完全独立的

4.4 VIPER 的缺点

  1. 代码量巨大:一个简单功能可能需要 5 个文件
  2. 学习成本高:新成员需要理解整个 VIPER 体系
  3. 过度设计风险:对于简单页面,VIPER 是杀鸡用牛刀
  4. 组件间通信复杂:需要定义大量协议

4.5 什么时候用 VIPER

适合不适合
大型 App(50+ 个页面)小中型 App
团队 10+ 人并行开发小团队
需要模块间解耦简单页面为主
长期维护的核心模块快速迭代的 MVP
对测试覆盖率要求极高测试不是优先项

五、Clean Architecture:企业级方案

5.1 Clean Architecture 分层

Clean Architecture(来自 Robert C. Martin)将系统分为多层,每层只依赖内层:

┌─────────────────────────────────────────────┐
│              Presentation Layer              │
│         (Views, ViewModels, Controllers)     │
└──────────────────────┬──────────────────────┘
                       │ 依赖
┌──────────────────────▼──────────────────────┐
│              Application Layer               │
│          (Use Cases, Commands)              │
└──────────────────────┬──────────────────────┘
                       │ 依赖
┌──────────────────────▼──────────────────────┐
│                Domain Layer                  │
│    (Entities, Repository Interfaces)         │
└──────────────────────▲──────────────────────┘
                       │ 实现
┌──────────────────────▼──────────────────────┐
│             Infrastructure Layer             │
│    (Repository Impl, API, DB, External)      │
└─────────────────────────────────────────────┘

5.2 代码示例

// ================= Domain Layer =================
// 实体 - 完全独立,不依赖任何外部框架
struct User: Equatable {
    let id: UUID
    let name: String
    let email: String
}

// 仓储接口 - 领域层定义接口,基础设施层实现
protocol UserRepository {
    func getUser(id: UUID) async throws -> User
    func saveUser(_ user: User) async throws
}

// ================= Application Layer =================
// Use Case - 应用业务逻辑
protocol GetUserUseCaseProtocol {
    func execute(userId: UUID) async throws -> User
}

class GetUserUseCase: GetUserUseCaseProtocol {
    private let repository: UserRepository

    init(repository: UserRepository) {
        self.repository = repository
    }

    func execute(userId: UUID) async throws -> User {
        let user = try await repository.getUser(id: userId)
        // 可以在这里添加额外的业务规则
        return user
    }
}

// ================= Infrastructure Layer =================
// 仓储实现
class UserRepositoryImpl: UserRepository {
    private let networkClient: NetworkClient
    private let cache: UserCache

    func getUser(id: UUID) async throws -> User {
        // 先查缓存
        if let cached = cache.get(id) {
            return cached
        }
        // 再请求网络
        let dto = try await networkClient.get("/users/\(id.uuidString)")
        let user = UserDTO.toDomain(dto)
        cache.set(user)
        return user
    }

    func saveUser(_ user: User) async throws {
        try await networkClient.post("/users", body: UserDTO.fromDomain(user))
        cache.set(user)
    }
}

// ================= Presentation Layer =================
class UserDetailViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false

    private let getUserUseCase: GetUserUseCaseProtocol

    func loadUser(id: UUID) async {
        isLoading = true
        defer { isLoading = false }
        user = try? await getUserUseCase.execute(userId: id)
    }
}

5.3 Clean Architecture 的优点

  1. 依赖规则清晰:内层完全不知道外层存在
  2. 业务逻辑可独立测试:不依赖 UI、数据库、网络
  3. 技术替换成本低:换数据库、换网络库,只需改 Infrastructure 层
  4. 适合企业级项目:大型团队和长期维护

5.4 Clean Architecture 的缺点

  1. 初期投入大:需要定义大量接口和分层结构
  2. 代码量最多:最简单的功能也要跨越 4 层
  3. 需要 DI 容器:依赖注入的管理会很复杂
  4. 不适合快速迭代:前期设计时间较长

5.5 什么时候用 Clean Architecture

适合不适合
企业级大型 App小型 App
多团队协作小团队
需要长期维护短期项目
业务逻辑复杂且稳定业务快速变化
需要可插拔的基础设施技术栈固定

六、决策树:如何选架构

项目启动
    │
    ▼
项目规模多大?
    │
    ├── < 10 个页面 ──→ MVC(直接用,别想太多)
    │
    ├── 10-50 个页面 ──→ 业务逻辑复杂吗?
    │       │
    │       ├── 否 ──→ MVVM(足够)
    │       │
    │       └── 是 ──→ MVVM + 模块化
    │               (按功能拆分模块,每个模块内部 MVVM)
    │
    └── 50+ 个页面 ──→ Clean Architecture
            │
            └── 团队 10+ 人? ──→ VIPER(模块内)
                              或按功能域拆分的 Clean Architecture

七、架构演进路径

7.1 渐进式演进

不要一开始就上最复杂的架构。推荐演进路径:

阶段1: MVC(启动)
  └── 项目 < 10 个页面,或验证阶段

阶段2: MVC → MVVM(当 Controller 开始膨胀)
  └── 识别出数据绑定价值高的页面,先改造这些页面

阶段3: MVVM → Clean Architecture(当团队规模扩大)
  └── 按功能域拆分,每个域有自己的 Repository 和 Use Cases

阶段4: 模块化(当代码库太大)
  └── 抽出独立的 Module/Framework,独立仓库独立发布

7.2 迁移策略

永远不要大爆炸式重构。 正确的做法是:

  1. 新功能用新架构:新增功能直接用目标架构
  2. 渐进式迁移旧代码:当旧代码需要修改时,顺便迁移
  3. 保持新旧共存:在很长一段时间内,两种架构会同时存在
  4. 用测试保护:迁移前先加测试,确保迁移后行为不变

八、架构评审清单

无论选择哪种架构,上线前请用这个清单检查:

检查项说明
Controller / ViewModel < 300 行超过说明有膨胀趋势
View 只做 UI,不做逻辑业务逻辑应该在 ViewModel / Interactor
Model 是纯数据不包含网络、持久化等基础设施代码
依赖方向清晰数据流向是否符合架构的预期方向
单元测试覆盖 ViewModel / Interactor不能测试的代码说明架构有问题
模块边界明确模块之间通过接口通信,不直接依赖

总结

架构适用规模代码量测试友好度学习曲线推荐指数
MVC小型⭐⭐⭐⭐
MVVM中型⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
VIPER大型⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Clean Architecture企业级⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

记住:架构是工具,不是目标。选择能解决当前问题的最简单架构,然后在问题出现时再演进。


下篇预告

下一篇文章我们将深入分析 MVC 架构:为什么 Apple 默认选择它,以及什么时候应该「超越」它——识别 Massive View Controller 的早期信号,以及具体的拆分策略。


如果你觉得这篇文章有收获,欢迎点赞。你的支持是我持续输出的动力。