第二部分:MVVM 在 SwiftUI 中的实现
第5章:开发 ViewModel 层
处理用户输入与业务逻辑
在 MVVM 架构中,ViewModel 层是连接 View 和 Model 的桥梁,负责处理用户输入并执行业务逻辑。本节将深入探讨如何在 SwiftUI 中实现这一关键功能。
1. 用户输入的响应机制
- 输入绑定:通过
@Published属性包装器和ObservableObject协议,ViewModel 可以暴露可观察的属性,供 View 层绑定。 - 事件处理:ViewModel 提供方法供 View 调用,例如按钮点击或表单提交。这些方法通常修改 Model 数据或触发网络请求。
class TodoViewModel: ObservableObject {
@Published var todos: [Todo] = []
func addTodo(title: String) {
let newTodo = Todo(title: title, isCompleted: false)
todos.append(newTodo)
}
func toggleCompletion(for todo: Todo) {
if let index = todos.firstIndex(where: { $0.id == todo.id }) {
todos[index].isCompleted.toggle()
}
}
}
2. 业务逻辑的实现原则
- 单一职责:每个 ViewModel 方法应只负责一个明确的业务逻辑。
- 可测试性:避免在 ViewModel 中直接引用 SwiftUI 视图或环境对象,以便于单元测试。
- 副作用管理:异步操作(如网络请求)应通过 Combine 或 Swift Concurrency 处理,并确保线程安全。
3. 与 Model 层的交互
- 数据转换:ViewModel 负责将 Model 数据转换为 View 可直接使用的格式(例如日期格式化)。
- 持久化操作:调用 Model 层的方法保存或加载数据,通常通过依赖注入解耦。
func loadTodos() async {
do {
let fetchedTodos = try await TodoAPI.fetchTodos()
DispatchQueue.main.async { self.todos = fetchedTodos }
} catch {
handleError(error)
}
}
4. 错误处理与用户反馈
- 错误传递:通过
@Published属性或 Combine 的Error事件通知 View 层。 - 状态管理:使用枚举定义加载状态(如
loading/success/failure),驱动 UI 更新。
enum DataState {
case idle, loading, loaded, error(Error)
}
class TodoViewModel: ObservableObject {
@Published var state: DataState = .idle
func loadTodos() {
state = .loading
Task {
do {
let todos = try await TodoAPI.fetchTodos()
state = .loaded(todos)
} catch {
state = .error(error)
}
}
}
}
案例:Todo 应用的业务逻辑实现
假设我们需要实现一个支持搜索的 Todo 列表:
- ViewModel 暴露
searchText属性和filteredTodos计算属性。 - 使用 Combine 的
debounce避免频繁搜索请求。 - 在 View 中通过
TextField绑定searchText,并显示filteredTodos。
class TodoViewModel: ObservableObject {
@Published var todos: [Todo] = []
@Published var searchText: String = ""
var filteredTodos: [Todo] {
guard !searchText.isEmpty else { return todos }
return todos.filter { $0.title.localizedCaseInsensitiveContains(searchText) }
}
}
通过以上模式,ViewModel 能有效隔离业务逻辑与 UI,保持代码的可维护性和可测试性。
