SwiftUI 16-MVVM 架构模式详解:构建更清晰可维护的 UI 结构

在 SwiftUI 开发中,MVVM(Model-View-ViewModel) 是 Apple 推荐的架构模式。它通过将 视图逻辑业务逻辑 解耦,显著提升代码的 可读性可维护性可测试性。本文将深入讲解 MVVM 的核心概念、SwiftUI 中的应用,并通过一个用户列表示例展示其实现。

一、什么是 MVVM?

MVVM 是一种 UI 架构设计模式,将代码分为三个核心部分:

角色 职责说明
Model 定义数据结构,代表应用的核心数据,保持独立性和高复用性。
View 负责UI 展示用户交互,仅处理数据的呈现,保持简单和专注。
ViewModel 充当 View 和 Model 的桥梁,负责业务逻辑数据转换状态管理,通过数据绑定驱动 UI 更新。

核心思想:ViewModel 将 Model 的数据转换为适合 View 显示的格式,并通过 SwiftUI 的响应式机制(如 @Published)自动更新界面。

数据获取(如网络请求、数据库操作)通常由独立的 服务层 处理,而不是放在 Model 中,以确保 Model 的纯净和复用性。

二、为什么 SwiftUI 天然适合 MVVM?

SwiftUI 的 声明式语法数据驱动 特性与 MVVM 高度契合:

  • 响应式数据绑定@State@StateObject@ObservedObject 等属性包装器让 View 自动响应数据变化。
  • 单一真相来源:ViewModel 借助 ObservableObject@Published 成为数据的核心管理中心。
  • 清晰分层:SwiftUI 的 View 专注于 UI 呈现,便于与 ViewModel 和 Model 分离。

这些特性使 MVVM 在 SwiftUI 中实现起来直观高效,特别适合构建可维护的大型应用。

三、MVVM 架构实战示例

以下通过一个用户列表的案例,展示如何在 SwiftUI 中应用 MVVM 架构,并引入独立的服务层处理数据获取。

1. Model 层:定义数据结构

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

说明

  • User 是 Model 层的核心,定义了数据的结构,符合 Identifiable(用于 SwiftUI 列表渲染)和 Codable(支持 JSON 序列化)。
  • Model 仅负责数据表示,不包含任何数据获取或业务逻辑,高度可复用。

2. 服务层:处理数据获取

数据获取逻辑(网络请求、数据库操作等)由独立的 服务层 负责,通过协议实现灵活性。

import Combine

// 定义数据获取协议
protocol UserFetchable {
    func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void)
}

// 网络服务实现
class NetworkService: UserFetchable {
    func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void) {
        // 模拟网络请求
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
            let users = [
                User(id: 1, name: "Alice", email: "alice@example.com"),
                User(id: 2, name: "Bob", email: "bob@example.com"),
                User(id: 3, name: "Charlie", email: "charlie@example.com")
            ]
            completion(.success(users))
            
            // 模拟错误情况(可取消注释测试)
            /*
            let error = NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "网络请求失败"])
            completion(.failure(error))
            */
        }
    }
}

说明

  • UserFetchable 协议定义了数据获取接口,允许替换为其他实现(如数据库服务)。
  • NetworkService 模拟网络请求,实际项目中可使用 URLSession 调用 API。
  • 服务层独立于 Model,确保 Model 的复用性。

3. ViewModel 层:处理业务逻辑与状态管理

ViewModel 协调服务层获取数据,处理业务逻辑,并管理 UI 状态。

import Combine

class UserListViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false
    @Published var errorMessage: String?

    private let userService: UserFetchable

    // 依赖注入服务,默认为 NetworkService
    init(userService: UserFetchable = NetworkService()) {
        self.userService = userService
    }

    func fetchUsers() {
        isLoading = true
        errorMessage = nil

        userService.fetchUsers { [weak self] result in
            DispatchQueue.main.async {
                self?.isLoading = false
                switch result {
                case .success(let users):
                    // 业务逻辑:过滤名字以 "A" 开头的用户
                    self?.users = users.filter { $0.name.starts(with: "A") }
                case .failure(let error):
                    self?.errorMessage = error.localizedDescription
                }
            }
        }
    }
}

说明

  • ViewModel 通过依赖注入接收 UserFetchable 服务,增强灵活性。
  • 包含业务逻辑(如过滤用户)和状态管理(如 isLoadingerrorMessage)。
  • 不直接处理网络请求,依赖服务层,保持职责单一。

4. View 层:展示 UI,绑定 ViewModel

View 负责展示 ViewModel 提供的数据,并处理用户交互。

import SwiftUI

struct UserListView: View {
    @StateObject private var viewModel = UserListViewModel()

    var body: some View {
        NavigationView {
            Group {
                if viewModel.isLoading {
                    ProgressView("加载中...")
                        .progressViewStyle(.circular)
                } else if let error = viewModel.errorMessage {
                    Text("错误:\(error)")
                        .foregroundColor(.red)
                        .padding()
                } else if viewModel.users.isEmpty {
                    Text("暂无符合条件的用户")
                        .font(.subheadline)
                        .foregroundColor(.gray)
                } else {
                    List(viewModel.users) { user in
                        VStack(alignment: .leading, spacing: 4) {
                            Text(user.name)
                                .font(.headline)
                            Text(user.email)
                                .font(.subheadline)
                                .foregroundColor(.secondary)
                        }
                    }
                }
            }
            .navigationTitle("用户列表")
            .onAppear {
                viewModel.fetchUsers()
            }
        }
    }
}

struct UserListView_Previews: PreviewProvider {
    static var previews: some View {
        UserListView()
    }
}

说明

  • View 仅展示 viewModel.users,并根据 isLoadingerrorMessage 显示不同状态。
  • 通过 @StateObject 管理 ViewModel 生命周期。
  • 用户交互(如页面加载时触发 fetchUsers)通过 ViewModel 间接调用服务层。

四、MVVM 的最佳实践

为确保 MVVM 架构高效、可维护,建议遵循以下实践:

  1. 保持 Model 纯净:Model 只定义数据结构(如 User),不包含数据获取或业务逻辑,确保高复用性。
  2. 独立服务层:将网络请求、数据库操作等放在独立的服务层(如 NetworkService),通过协议支持替换。
  3. ViewModel 职责专注:ViewModel 负责业务逻辑和状态管理,协调服务层获取数据。
  4. View 简单化:View 只处理 UI 展示和用户交互,避免直接访问服务层或 Model。
  5. 依赖注入:ViewModel 使用协议(如 UserFetchable)注入服务,便于测试和替换数据源。
  6. 处理边缘情况:ViewModel 管理加载状态、错误状态,View 展示相应 UI(如空数据提示)。
  7. 单元测试:Model 和 ViewModel 独立于 UI,易于编写单元测试。

五、MVVM 架构的优势

优势 说明
解耦性好 UI、业务逻辑、数据层分离,降低耦合,便于维护和扩展。
代码复用 Model 和服务层可跨模块复用,ViewModel 可服务多个 View。
易于测试 ViewModel 和服务层独立于 UI,方便编写单元测试,验证业务逻辑和数据获取。
结构清晰 分层架构让代码组织更清晰,适合团队协作和大型项目。

六、总结

MVVM 是 SwiftUI 推荐的架构模式,充分利用了其 声明式语法响应式数据绑定 特性。通过将 Model 限制为数据结构,引入独立的服务层处理数据获取,MVVM 让 UI(View)、业务逻辑(ViewModel)和数据层(Model + 服务层)各司其职,构建清晰、可维护的应用架构。

在实际项目中,建议:

  • 使用协议(如 UserFetchable)设计服务层,增强灵活性。
  • 通过依赖注入让 ViewModel 可测试。
  • 从简单案例入手,逐步将 MVVM 应用于复杂场景,结合错误处理和单元测试,构建健壮的 SwiftUI 应用。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容