第5章:开发 ViewModel 层
ViewModel 的职责与设计
核心职责
ViewModel 在 MVVM 架构中扮演着 业务逻辑处理器 和 数据转换器 的双重角色,其核心职责包括:
- 数据准备:从 Model 层获取原始数据并转换为 View 层可直接使用的格式。
- 状态管理:通过
@Published属性包装器维护 UI 状态,驱动视图更新。 - 用户交互处理:接收来自 View 层的用户输入事件(如按钮点击),触发业务逻辑。
- 依赖协调:管理网络请求、本地存储等外部服务的调用。
设计原则
1. 单一职责原则
// 反例:混杂多个业务域的 ViewModel
class UserDashboardViewModel {
func loadUserProfile() { ... }
func fetchLatestOrders() { ... } // ❌ 订单逻辑不应存在于用户面板VM
}
// 正例:拆分职责
class UserProfileViewModel { ... }
class OrderListViewModel { ... }
2. 无视图引用
ViewModel 不应持有任何 SwiftUI View 的引用,仅通过数据绑定与 View 通信:
// 反例
class TodoViewModel {
var rootView: ContentView? // ❌ 禁止持有视图引用
}
3. 可测试性设计
- 通过协议抽象外部依赖
- 使用依赖注入初始化服务
protocol DataServiceProtocol {
func fetchTodos() async throws -> [Todo]
}
class TodoViewModel {
private let service: DataServiceProtocol
init(service: DataServiceProtocol) { ... } // ✅ 可注入Mock测试
}
典型结构示例
class ArticleViewModel: ObservableObject {
// MARK: - Published Properties
@Published var articles: [Article] = []
@Published var isLoading = false
@Published var errorMessage: String?
// MARK: - Dependencies
private let apiClient: APIClientProtocol
// MARK: - Initialization
init(apiClient: APIClientProtocol = APIClient()) {
self.apiClient = apiClient
}
// MARK: - Public Methods
@MainActor
func loadArticles() async {
isLoading = true
do {
articles = try await apiClient.fetchArticles()
errorMessage = nil
} catch {
errorMessage = "加载失败: \(error.localizedDescription)"
}
isLoading = false
}
func deleteArticle(_ id: UUID) { ... }
}
生命周期管理
- 使用
ObservableObject协议实现自动视图更新 - 对于复杂场景可采用
StateObject管理生命周期:
struct ArticleListView: View {
@StateObject var viewModel = ArticleViewModel() // ✅ 正确生命周期管理
var body: some View { ... }
}
常见陷阱
- 过度臃肿:当单个 ViewModel 超过 400 行代码时,应考虑拆分
- 直接修改 View 状态:所有状态变更应通过 ViewModel 的 published 属性
- 忽略线程安全:确保
@Published更新发生在主线程(使用@MainActor)
最佳实践提示:为每个独立屏幕或功能模块创建专属 ViewModel,避免创建"上帝对象"式的全局 ViewModel。
