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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 02:36:02