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

URLSessionDataTaskPublisher错误处理异常及重复调用问题排查

问题解决:首次API调用错误时runSyncTransactions重复调用的问题

问题背景

开发iOS应用时,通过远程API按周期获取交易数据,API限制每10秒仅可调用一次,IP封禁风险需规避。当网络返回HTTP错误(404、500等)或API返回预定义错误(认证令牌无效、日期错误等)时,需立即停止后续API调用队列。

当前实现存在问题:首次API调用出现不可达或预定义错误时,runSyncTransactions总会被调用两次。核心原因是网络请求为异步操作,代码未等待请求完成就检查lastApiError,此时lastApiError仍为nil,错误检查失效,导致循环继续执行下一次调用。

问题根源

runSyncTransactions作为异步函数,使用Combine的dataTaskPublisher发起网络请求,但未等待异步流完成就执行后续错误检查逻辑。网络请求的回调(错误或成功)会滞后于错误检查代码,导致lastApiError无法及时捕获错误,循环无法终止。

解决方案

将Combine异步流转换为async/await风格,让runSyncTransactions真正等待网络请求完成后再返回,确保错误能及时抛出并终止后续循环。

修改后的核心代码

private func runSyncTransactions(dateFrom: Date, dateTo: Date) async throws {
    if stopSync {
        stopSync = false
        throw SomeError.synchronizationStoppedByUser
    }

    let dateFromTimestamp = Int(dateFrom.timeIntervalSince1970)
    let dateToTimestamp = Int(dateTo.timeIntervalSince1970)
    
    guard let url = URL(string: "\(apiURLString)/\(dateFromTimestamp)/\(dateToTimestamp)") else {
        throw SomeError.error1
    }
    
    var request = URLRequest(url: url)
    request.httpMethod = "GET"

    do {
        let (data, response) = try await URLSession.shared.data(for: request)
        
        // 校验HTTP响应状态码
        guard let httpResponse = response as? HTTPURLResponse,
              200..<300 ~= httpResponse.statusCode else {
            // 解析API自定义错误
            if let apiError = try? JSONDecoder().decode(ApiErrorResponse.self, from: data) {
                let error = ApiError.makeError(errorResponse: apiError)
                print("Error receiving transactions: \(apiError.errorDescription)")
                throw error
            } else {
                throw SomeError.undefinedError
            }
        }
        
        // 解析成功返回的数据
        let returnedItems = try JSONDecoder().decode([TransactionItem].self, from: data)
        print("Received transactions: \(returnedItems.count)")
        
        let transactionModelsTemp = returnedItems.map {
            TransactionConvertor.transactionItemToTransactonModel(item: $0)
        }
        
        // 回到主线程更新UI相关的Published属性
        DispatchQueue.main.async {
            self.transactionModels = transactionModelsTemp
        }
    } catch {
        print("Error sync transactions: \(error.localizedDescription)")
        throw error
    }
}

关键修改点

  1. 替换异步实现:移除Combine的AnyCancellable订阅,改用URLSession.data(for:)的async/await版本,让函数等待网络请求完成后再执行后续逻辑。
  2. 简化错误处理:直接在do/catch块中处理所有错误,无需依赖lastApiError变量,彻底避免异步竞态问题。
  3. 主线程更新UI:async/await默认在后台线程执行,解析成功后通过DispatchQueue.main.async更新@Published属性,符合UI更新规则。
  4. 移除冗余变量:删除lastApiError,通过直接抛出错误终止后续循环,逻辑更简洁可靠。

额外优化提示

原代码中dateTo参数每次都使用同一个固定日期(Calendar.current.date(byAdding: .day, value: -29, to: dateNow)!),建议修正为与dateFrom对应的区间(例如dateTo: Calendar.current.date(byAdding: .day, value: 29, to: date)),确保请求的日期范围符合业务需求。

内容的提问来源于stack exchange,提问作者Serhii R

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 21:24:52