14.1_在ViewModel中封装网络请求逻辑
为什么要在ViewModel中封装网络请求?
将网络请求逻辑封装在 ViewModel 中是 MVVM 架构的核心优势之一。这样做可以显著提升代码的可维护性和可测试性。想象一下,如果你的 ViewController 充斥着网络请求代码,它会变得臃肿且难以管理。通过将这些逻辑移到 ViewModel,你将 View 层从数据获取的复杂性中解放出来。这使得 ViewController 专注于 UI 布局和事件处理,而 ViewModel 则专注于业务逻辑和数据管理。 🚀
构建网络服务层
为了更好地封装网络请求,我们通常会创建一个专门的网络服务层。这个服务层负责处理所有的 HTTP 请求细节,例如构建 URL、设置请求头、处理响应等。你可以定义一个协议(protocol)来抽象网络请求的接口,这样 ViewModel 就可以依赖这个协议而不是具体的实现。这为单元测试提供了极大的便利,因为你可以轻松地模拟网络响应。
- 定义网络请求协议:swift
protocol NetworkService { func fetchData<T: Decodable>(from url: URL, completion: @escaping (Result<T, Error>) -> Void) } - 实现具体的网络服务:swift这里我们定义了一个
class URLSessionNetworkService: NetworkService { func fetchData<T: Decodable>(from url: URL, completion: @escaping (Result<T, Error>) -> Void) { URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { completion(.failure(error)) return } guard let data = data else { completion(.failure(NetworkError.noData)) return } do { let decodedObject = try JSONDecoder().decode(T.self, from: data) completion(.success(decodedObject)) } catch { completion(.failure(error)) } }.resume() } }NetworkError枚举来处理特定的网络错误,例如noData。
ViewModel如何使用网络服务
ViewModel 会持有 NetworkService 的一个实例。当 ViewModel 需要获取数据时,它会调用 NetworkService 上的相应方法。例如,一个 UserViewModel 可能需要从服务器获取用户列表。它会通过 NetworkService 发起请求,并在收到响应后更新其内部的可观察属性。这样,ViewController 订阅这些属性,就能自动更新 UI。
class UserListViewModel {
private let networkService: NetworkService
// 定义一个可观察的属性来存储用户数据
var users: Observable<[User]> = Observable([])
var isLoading: Observable<Bool> = Observable(false)
var errorMessage: Observable<String?> = Observable(nil)
init(networkService: NetworkService) {
self.networkService = networkService
}
func fetchUsers() {
isLoading.value = true
errorMessage.value = nil
guard let url = URL(string: "https://api.example.com/users") else {
errorMessage.value = "无效的URL"
isLoading.value = false
return
}
networkService.fetchData(from: url) { [weak self] (result: Result<[User], Error>) in
DispatchQueue.main.async {
self?.isLoading.value = false
switch result {
case .success(let fetchedUsers):
self?.users.value = fetchedUsers
case .failure(let error):
self?.errorMessage.value = error.localizedDescription
}
}
}
}
}在这个例子中,UserListViewModel 负责调用 networkService.fetchData。它还管理 isLoading 和 errorMessage 状态,这些状态可以直接绑定到 UI,提供更好的用户体验。 🌟
异步操作与线程管理
网络请求本质上是异步操作。这意味着请求会在后台线程中执行,而不会阻塞主线程。当数据返回时,我们需要确保 UI 更新操作在主线程上进行。在上面的 fetchUsers 方法中,我们使用了 DispatchQueue.main.async 来确保 users、isLoading 和 errorMessage 的更新发生在主线程。这是至关重要的,因为所有 UI 相关的操作都必须在主线程上执行,否则会导致应用崩溃或不可预测的行为。 ⚠️
错误处理与状态管理
在 ViewModel 中封装网络请求时,错误处理是不可或缺的一部分。当网络请求失败时,ViewModel 应该能够捕获错误并将其报告给 View 层。这通常通过更新一个 errorMessage 可观察属性来实现。同时,ViewModel 还应该管理加载状态(isLoading),以便 View 可以显示加载指示器,提升用户体验。一个健壮的 ViewModel 会清晰地定义这些状态,并确保它们在整个网络请求生命周期中得到正确更新。这不仅让你的应用更稳定,也让用户体验更流畅。 👍