Skip to content

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。

swift
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。它还管理 isLoadingerrorMessage 状态,这些状态可以直接绑定到 UI,提供更好的用户体验。 🌟

异步操作与线程管理

网络请求本质上是异步操作。这意味着请求会在后台线程中执行,而不会阻塞主线程。当数据返回时,我们需要确保 UI 更新操作在主线程上进行。在上面的 fetchUsers 方法中,我们使用了 DispatchQueue.main.async 来确保 usersisLoadingerrorMessage 的更新发生在主线程。这是至关重要的,因为所有 UI 相关的操作都必须在主线程上执行,否则会导致应用崩溃或不可预测的行为。 ⚠️

错误处理与状态管理

ViewModel 中封装网络请求时,错误处理是不可或缺的一部分。当网络请求失败时,ViewModel 应该能够捕获错误并将其报告给 View 层。这通常通过更新一个 errorMessage 可观察属性来实现。同时,ViewModel 还应该管理加载状态(isLoading),以便 View 可以显示加载指示器,提升用户体验。一个健壮的 ViewModel 会清晰地定义这些状态,并确保它们在整个网络请求生命周期中得到正确更新。这不仅让你的应用更稳定,也让用户体验更流畅。 👍

本站使用 VitePress 制作