基于Async/Await复用网络请求获取多组书单数据问题求助
解决方案:在ViewModel中正确处理多组书单的异步请求
核心思路
把所有异步请求逻辑收拢到ListsViewModel中,通过批量异步处理+状态管理解决顺序和效率问题,避免视图层直接处理网络请求。
步骤1:完善BookService的批量请求方法
给BookService添加批量获取书籍的异步方法,自带错误处理,同时支持单本和多本请求:
class BookService { // 单本书籍请求 func fetchBook(by id: String) async throws -> Book { let url = URL(string: "https://www.googleapis.com/books/v1/volumes/\(id)")! let (data, response) = try await URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw BookServiceError.invalidResponse } return try JSONDecoder().decode(Book.self, from: data) } // 批量书籍请求,返回[ID: Book结果]字典,方便分组匹配 func fetchBooks(by ids: [String]) async -> [String: Result<Book, Error>] { let tasks = ids.map { id in Task { () -> (String, Result<Book, Error>) in do { let book = try await fetchBook(by: id) return (id, .success(book)) } catch { return (id, .failure(error)) } } } let results = await tasks.map { try! await $0.value } return Dictionary(uniqueKeysWithValues: results) } enum BookServiceError: Error { case invalidResponse case decodingFailed } }
步骤2:在ListsViewModel中管理状态与异步逻辑
ViewModel维护四组书单的加载状态、数据、错误信息,用Task封装异步操作,避免重复请求:
class ListsViewModel: ObservableObject { @Published var currentList: [Book] = [] @Published var nextList: [Book] = [] @Published var doneList: [Book] = [] @Published var sharingList: [Book] = [] @Published var isLoading = false @Published var errorMessage: String? private let bookService: BookService private var loadTask: Task<Void, Never>? init(bookService: BookService = BookService()) { self.bookService = bookService } // 加载所有书单的入口方法 func loadAllLists() { // 取消之前的任务,避免重复请求 loadTask?.cancel() isLoading = true errorMessage = nil loadTask = Task { [weak self] in guard let self = self, !Task.isCancelled else { return } do { // 1. 从Firestore获取四组书单的ID数组 let currentIds = try await self.fetchIdsFromFirestore(for: "current") let nextIds = try await self.fetchIdsFromFirestore(for: "next") let doneIds = try await self.fetchIdsFromFirestore(for: "done") let sharingIds = try await self.fetchIdsFromFirestore(for: "sharing") // 2. 合并所有ID批量请求,减少API调用次数 let allIds = currentIds + nextIds + doneIds + sharingIds let bookResults = await self.bookService.fetchBooks(by: allIds) // 3. 按分组匹配书籍,主线程更新UI await MainActor.run { self.currentList = currentIds.compactMap { guard case .success(let book) = bookResults[$0] else { return nil } return book } self.nextList = nextIds.compactMap { guard case .success(let book) = bookResults[$0] else { return nil } return book } self.doneList = doneIds.compactMap { guard case .success(let book) = bookResults[$0] else { return nil } return book } self.sharingList = sharingIds.compactMap { guard case .success(let book) = bookResults[$0] else { return nil } return book } } } catch { await MainActor.run { self.errorMessage = "加载书单失败:\(error.localizedDescription)" } } await MainActor.run { self.isLoading = false } } } // 从Firestore获取对应分组的书籍ID private func fetchIdsFromFirestore(for listType: String) async throws -> [String] { let snapshot = try await Firestore.firestore().collection("userLists").document(listType).getDocument() return snapshot.data()?["bookIds"] as? [String] ?? [] } // 页面消失时取消任务,避免无效请求 func cancelLoadTask() { loadTask?.cancel() } }
步骤3:视图层正确调用ViewModel
在个人页面视图中,通过onAppear触发加载,onDisappear取消任务:
struct ProfileView: View { @StateObject private var viewModel = ListsViewModel() var body: some View { ScrollView { if viewModel.isLoading { ProgressView("加载中...") } else if let error = viewModel.errorMessage { Text(error) .foregroundColor(.red) } else { BookListView(title: "当前阅读", books: viewModel.currentList) BookListView(title: "待读", books: viewModel.nextList) BookListView(title: "已读完", books: viewModel.doneList) BookListView(title: "分享中", books: viewModel.sharingList) } } .onAppear { viewModel.loadAllLists() } .onDisappear { viewModel.cancelLoadTask() } } } struct BookListView: View { let title: String let books: [Book] var body: some View { Section { Text(title) .font(.headline) ForEach(books) { book in BookRow(book: book) } } .padding() } }
关键优化点
- 批量请求合并:合并四组ID一次性请求,减少API调用次数,规避Google Books API限流
- 状态集中管理:所有加载状态、错误信息由ViewModel维护,视图仅负责渲染
- 任务取消机制:页面消失时取消未完成请求,避免内存泄漏和无效请求
- 主线程更新UI:用
MainActor.run确保UI操作在主线程执行,避免线程冲突
内容的提问来源于stack exchange,提问作者amelia
相关产品推荐
相关产品推荐

