You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Combine链式执行两次网络请求,失败时终止链路并生成ViewModel

Combine链式调用实现顺序网络请求并组装ViewModel

我来帮你搞定这个Combine链式调用的需求,刚好能实现你要的先请求子分类、再请求分类,第一个失败就终止链路,最后组装ViewModel的完整流程。

核心思路

  1. 用flatMap实现顺序请求:只有第一个子分类请求成功后,才会发起分类请求,任何一步失败都会直接终止整个链路。
  2. 将子分类数据先转换成ViewModel并存储为字典(用ID做key),方便后续快速匹配分类对应的子分类ViewModel。
  3. 最后将分类数据和对应的子分类ViewModel组合成最终的CategoryViewModel数组。

具体实现

首先在你的CategoriesService里添加一个新方法,专门处理整个流程:

class CategoriesService: Service, ErrorManager {
    // 你的现有代码...
    
    func getAllCategoriesWithSubviewModels() -> AnyPublisher<[CategoryEntityViewModel], ServerError> {
        return getAllSubcategories()
            // 先获取子分类,成功后再获取分类,同时传递子分类数据
            .flatMap { [weak self] subcategories -> AnyPublisher<([SubcategoryEntity], [CategoryEntity]), ServerError> in
                guard let self = self else {
                    return Fail(error: ServerError.unknown).eraseToAnyPublisher()
                }
                return self.getAllCategories()
                    .map { categories in (subcategories, categories) }
                    .eraseToAnyPublisher()
            }
            // 将两个实体数组转换成ViewModel数组
            .map { subcategories, categories in
                // 先创建子分类ViewModel的字典,用ID做key,方便快速查找
                let subViewModelDict = Dictionary(uniqueKeysWithValues: subcategories.map { sub in
                    (sub.id, SubcategoryEntityViewModel(subcategory: sub))
                })
                
                // 遍历分类,为每个分类匹配对应的子分类ViewModel
                return categories.map { category in
                    let matchedSubViewModels = category.subcategoriesIDS.compactMap { subId in
                        subViewModelDict[subId]
                    }
                    return CategoryEntityViewModel(category: category, subcategories: matchedSubViewModels)
                }
            }
            .receive(on: RunLoop.main)
            .eraseToAnyPublisher()
    }
}

关键细节解释

  • flatMap的作用:确保请求顺序执行,只有getAllSubcategories()成功返回后,才会触发getAllCategories()。如果第一个请求失败,整个链路会直接进入failure状态,不会发起第二个请求,完全符合你“第一个失败则终止”的要求。
  • 字典存储子分类ViewModel:用sub.id作为key,把SubcategoryViewModel存入字典,后续通过分类的subcategoriesIDS查找时,时间复杂度是O(1),比遍历整个子分类数组要高效得多,尤其是数据量较大时优势明显。
  • compactMap过滤无效ID:如果后端返回的subcategoriesIDS里有不存在的子分类ID,compactMap会自动过滤掉这些无效项,保证最终的SubcategoryViewModel数组都是有效的实例。

调用示例

在需要使用的类(比如ViewModel)里调用这个方法:

class SomeViewModel: ObservableObject {
    @Published var categoryViewModels: [CategoryEntityViewModel] = []
    private var cancellables = Set<AnyCancellable>()
    
    func loadCategories() {
        CategoriesService.shared.getAllCategoriesWithSubviewModels()
            .sink(receiveCompletion: { [weak self] completion in
                guard let self = self else { return }
                switch completion {
                case .failure(let error):
                    self.error = error // 假设你有一个@Published的error属性
                case .finished:
                    break
                }
            }, receiveValue: { [weak self] viewModels in
                guard let self = self else { return }
                self.categoryViewModels = viewModels
            })
            .store(in: &cancellables)
    }
}

这样整个流程就完全打通了:从顺序请求网络数据,到转换组装ViewModel,再到错误处理,都用Combine链式调用完成,代码简洁且符合响应式编程的风格。

内容的提问来源于stack exchange,提问作者Philip K.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:30:48