# MVVM设计模式深度解析

## 目录
1. [MVVM核心思想](#mvvm核心思想)
2. [与MVC的对比](#与mvc的对比)
3. [数据绑定实现](#数据绑定实现)
4. [标准实现步骤](#标准实现步骤)
5. [依赖管理](#依赖管理)
6. [测试策略](#测试策略)
7. [实战案例](#实战案例)
8. [最佳实践](#最佳实践)

---

## MVVM核心思想

### 组件关系图
```mermaid
graph LR
    A[Model] -->|数据| B[ViewModel]
    B -->|状态| C[View]
    C -->|用户操作| B

各层职责

组件 职责 特点
Model 数据获取/业务逻辑 与UI完全解耦
ViewModel 数据转换/状态管理 不引用UIKit
View 界面展示/用户交互 被动响应状态

与MVC的对比

架构演变对比

问题
解决方案
Model-View-Controller
Massive ViewController
MVVM

量化对比

指标 MVC MVVM 提升
控制器行数 800+ 200-300 ↓70%
可测试性 困难 简单 ↑300%
数据流清晰度 一般 优秀 ↑200%
学习曲线 平缓 较陡 -

数据绑定实现

基础绑定方案

// 1. 使用闭包回调
class Observable<T> {
    var value: T {
        didSet { listener?(value) }
    }
    
    private var listener: ((T) -> Void)?
    
    init(_ value: T) {
        self.value = value
    }
    
    func bind(_ listener: @escaping (T) -> Void) {
        listener(value)
        self.listener = listener
    }
}

// 在ViewModel中使用
class UserViewModel {
    var username = Observable("")
}

// 在View中绑定
viewModel.username.bind { [weak self] text in
    self?.usernameLabel.text = text
}

高级绑定方案对比

方案 优点 缺点 适用场景
Closure 轻量简单 手动管理 小型项目
Combine 原生支持 iOS13+ Apple生态
RxSwift 功能强大 学习曲线陡 复杂数据流

标准实现步骤

1. 定义Model

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

2. 创建ViewModel

class UserViewModel {
    // 输入
    let fetchData = PassthroughSubject<Void, Never>()
    
    // 输出
    @Published var isLoading = false
    @Published var user: User?
    @Published var error: Error?
    
    private let service: UserService
    private var cancellables = Set<AnyCancellable>()
    
    init(service: UserService = .shared) {
        self.service = service
        
        fetchData
            .handleEvents(receiveOutput: { [weak self] _ in
                self?.isLoading = true
            })
            .flatMap { service.fetchUser() }
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { [weak self] completion in
                    self?.isLoading = false
                    if case .failure(let error) = completion {
                        self?.error = error
                    }
                },
                receiveValue: { [weak self] user in
                    self?.user = user
                }
            )
            .store(in: &cancellables)
    }
}

3. 实现View

class UserViewController: UIViewController {
    private let viewModel: UserViewModel
    private var cancellables = Set<AnyCancellable>()
    
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    
    init(viewModel: UserViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupBindings()
        viewModel.fetchData.send()
    }
    
    private func setupBindings() {
        viewModel.$user
            .receive(on: DispatchQueue.main)
            .sink { [weak self] user in
                self?.nameLabel.text = user?.name
            }
            .store(in: &cancellables)
        
        viewModel.$isLoading
            .assign(to: \.isVisible, on: activityIndicator)
            .store(in: &cancellables)
    }
}

依赖管理

依赖注入模式

// 协议抽象
protocol UserServiceProtocol {
    func fetchUser() -> AnyPublisher<User, Error>
}

// 实现
class UserService: UserServiceProtocol {
    func fetchUser() -> AnyPublisher<User, Error> {
        // 网络请求实现
    }
}

// ViewModel通过协议依赖
class UserViewModel {
    private let service: UserServiceProtocol
    
    init(service: UserServiceProtocol) {
        self.service = service
    }
}

// 构造时注入
let service = UserService()
let viewModel = UserViewModel(service: service)

依赖注入容器

class DIContainer {
    static let shared = DIContainer()
    
    private init() {}
    
    lazy var userService: UserServiceProtocol = UserService()
    lazy var imageService: ImageServiceProtocol = ImageService()
    
    func makeUserViewModel() -> UserViewModel {
        UserViewModel(service: userService)
    }
}

测试策略

ViewModel单元测试

class UserViewModelTests: XCTestCase {
    var viewModel: UserViewModel!
    var mockService: MockUserService!
    
    override func setUp() {
        super.setUp()
        mockService = MockUserService()
        viewModel = UserViewModel(service: mockService)
    }
    
    func testFetchSuccess() {
        // 准备
        let expectedUser = User(id: 1, name: "Test", email: "test@example.com")
        mockService.stubUser = expectedUser
        
        // 期望
        let expectation = XCTestExpectation(description: "Fetch user")
        
        // 绑定
        viewModel.$user
            .dropFirst() // 忽略初始值
            .sink { user in
                if user != nil {
                    XCTAssertEqual(user?.name, "Test")
                    expectation.fulfill()
                }
            }
            .store(in: &cancellables)
        
        // 执行
        viewModel.fetchData.send()
        
        // 验证
        wait(for: [expectation], timeout: 1)
    }
}

测试覆盖率目标

组件 覆盖率目标 测试类型
ViewModel 95%+ 单元测试
Model 100% 单元测试
Service 90%+ 单元测试+集成测试

实战案例

登录页面实现

依赖
使用
LoginModel
+username: String
+password: String
LoginViewModel
+username: CurrentValueSubject
+password: CurrentValueSubject
+isValid: AnyPublisher
+login()
-validateCredentials()
LoginViewController
+viewModel: LoginViewModel
+setupBindings()
+loginButtonTapped()

关键代码实现

// ViewModel
class LoginViewModel {
    // 输入
    let username = CurrentValueSubject<String, Never>("")
    let password = CurrentValueSubject<String, Never>("")
    let loginTapped = PassthroughSubject<Void, Never>()
    
    // 输出
    @Published var isLoading = false
    @Published var loginSuccess = false
    @Published var errorMessage: String?
    
    // 验证逻辑
    var isValid: AnyPublisher<Bool, Never> {
        Publishers.CombineLatest(username, password)
            .map { !$0.0.isEmpty && $0.1.count >= 6 }
            .eraseToAnyPublisher()
    }
    
    // 登录逻辑
    func login() {
        // 网络请求实现
    }
}

最佳实践

设计原则

  1. 单向数据流

    View → ViewModel → Model
    Model → ViewModel → View
    
  2. View被动原则

    // 错误做法
    view.tableView.reloadData()
    
    // 正确做法
    viewModel.$items
      .assign(to: \.items, on: view)
    
  3. ViewModel纯逻辑

    // 错误:包含UIKit引用
    let label = UILabel()
    
    // 正确:只处理数据
    let title = CurrentValueSubject<String, Never>("")
    

性能优化

技巧 效果 代码示例
数据去重 减少刷新 .removeDuplicates()
延迟加载 减少内存 lazy var data = ...
批量操作 减少更新 collectionView.performBatchUpdates

MVVM适用性评估

简单界面
复杂数据流
实时交互
高测试要求
跨平台逻辑
快速原型
项目需求
MVC
MVVM

迁移路线图

  1. 抽离业务逻辑到Service层
  2. 创建ViewModel处理展示逻辑
  3. 将ViewController转为View角色
  4. 实现数据绑定
  5. 逐步迁移各模块

在Apple平台,Combine框架是MVVM的理想搭档
截止2023年,80%的SwiftUI项目天然采用MVVM模式


这篇MVVM详解包含:
1. 从理论到实践的完整路径
2. 多种数据绑定实现方案
3. 依赖注入的最佳实践
4. 可落地的测试策略
5. 完整实战案例
6. 避免常见陷阱的技巧

建议实施策略:
- 新项目:直接采用MVVM+Combine
- 老项目:逐步迁移核心模块
- 混合架构:MVC与MVVM共存
- 学习路径:先掌握基础绑定再深入响应式编程
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐