iOS14+下如何让Combine的dataTaskPublisher返回解码数据配合Async/Await合并双请求
实现方案
iOS 14 本身不支持原生 Swift Concurrency(async/await 是 iOS 15+ 特性),你可以根据需求选择以下两种兼容方案:
方案1:纯 Combine 实现(全兼容 iOS 14+)
首先把你的请求逻辑封装成可复用的泛型 Publisher,再用 Publishers.Zip 合并两个请求的结果:
第一步:封装通用请求 Publisher
import Combine func fetchData<T: Decodable>(for url: URL, type: T.Type) -> AnyPublisher<T, Error> { URLSession.shared.dataTaskPublisher(for: url) .subscribe(on: DispatchQueue.global(qos: .background)) .receive(on: DispatchQueue.main) .tryMap { (data, response) in guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw URLError(.badServerResponse) } NetworkTimer.shared.stop() return data } .decode(type: T.self, decoder: JSONDecoder()) .eraseToAnyPublisher() }
第二步:合并两个请求
// 替换为你自己的历史数据解码模型 struct HistoricalWeatherDecoder: Decodable {} // 合并后的目标结构体 struct CombinedData { let current: WeatherAPIDecoder let historical: HistoricalWeatherDecoder } class WeatherManager { // 用于持有请求生命周期,不要局部声明cancellable private var cancellables = Set<AnyCancellable>() func updateWeatherData(currentURL: URL, historicalURL: URL) { // 同时发起两个请求 let currentReq = fetchData(for: currentURL, type: WeatherAPIDecoder.self) let historicalReq = fetchData(for: historicalURL, type: HistoricalWeatherDecoder.self) // Zip会等待两个请求都完成后再输出结果,任意一个失败都会抛出错误 Publishers.Zip(currentReq, historicalReq) .sink { [weak self] completion in guard let self = self else { return } switch completion { case .failure(let error): // 处理请求失败逻辑 print("请求失败: \(error)") case .finished: break } } receiveValue: { [weak self] current, historical in guard let self = self else { return } let combinedData = CombinedData(current: current, historical: historical) // 处理组装后的合并数据 self.processCombinedData(combinedData) } .store(in: &cancellables) } }
注意:你原来的代码在
sink后直接调用.cancel()会导致请求刚发起就被取消,之前能运行属于巧合,正确做法是把 cancellable 存入全局的Set<AnyCancellable>持有,请求完成后会自动释放。
方案2:async/await 适配(iOS 15+ 可用,iOS 14 可做降级兼容)
如果要使用 async/await 语法,可以给 Publisher 加扩展,用 CheckedContinuation 包装成异步函数:
@available(iOS 15.0, *) extension Publisher { func getAsyncResult() async throws -> Output { try await withCheckedThrowingContinuation { continuation in var cancellable: AnyCancellable? cancellable = self .sink(receiveCompletion: { completion in switch completion { case .failure(let error): continuation.resume(throwing: error) case .finished: break } cancellable?.cancel() }, receiveValue: { value in continuation.resume(returning: value) }) } } }
之后就可以用你期望的 async/await 写法调用:
@available(iOS 15.0, *) func fetchCombinedData(currentURL: URL, historicalURL: URL) async throws -> CombinedData { // 并发发起两个请求 async let current = fetchData(for: currentURL, type: WeatherAPIDecoder.self).getAsyncResult() async let historical = fetchData(for: historicalURL, type: HistoricalWeatherDecoder.self).getAsyncResult() // 等待两个结果返回后组装 return try await CombinedData(current: current, historical: historical) }
内容的提问来源于stack exchange,提问作者Yrb
相关产品推荐
相关产品推荐

