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

Swift Combine实现基于成功响应的API最多3次递归重试

问题

需要在Swift Combine中实现:当API返回成功响应但status不是预期的Paid时,最多递归调用该API3次,每次重试间隔5秒。即使第三次仍未得到Paid状态,也要将showConfirmDetails设为true并继续流程。现有代码仅处理了错误重试场景,不清楚如何处理成功但不符合条件的重试需求。

原代码如下:

struct DetailModel {
    var status: String
}

func details(url: URL) -> AnyPublisher<DetailModel, NetworkCommunicationError> {
    let getDetailsRequest = NetworkRequest(url: url,
                                           queryType: .path,
                                           responseDataType: .json,
                                           isProprietary: true,
                                           useJsonSerialization: true)
    return network.handleRequest(request: getDetailsRequest)
}

func getDetialsById(url: URL) {
    self.responseModel = nil
    
    self.service.details(url: url)
        .receive(on: DispatchQueue.main)
        .sink( { (compleation) in
            if case let.failure(error) = completion {
                self.handleErrorCase(error)
            }
        } receiveValue: { (value) in
            self.responseModel = value
            if value.status != "Paid" {
                self.showConfirmDetails = false
                /*
                 Retry the same call self.service.details for 2 more times
                 with the delay of 5 seconds to check if i can get the value.status ==
                 Paid on the third try even if i dont get value.status == Paid status 
                 i will make
                 self.showConfirmDetails = true
                 and proceed with the status i have received
                */
            } else {
                self.showConfirmDetails = true
            }
        }).store(in: &self.cancellebles)
}
解决方案

核心思路是将「成功响应但状态不符合预期」的场景转换为自定义错误,借助Combine的重试逻辑处理,最后再捕获该错误并转为正常流程。

步骤1:定义自定义重试错误

先扩展原有网络错误类型,新增对应重试场景的错误(如果原有错误类型无法扩展,可自定义枚举包装):

extension NetworkCommunicationError {
    static let nonPaidStatus = Self(rawValue: "non_paid_status") // 根据你的错误类型实际初始化逻辑调整
}

步骤2:实现带条件的重试逻辑

修改getDetialsById函数,用tryMap转换不符合条件的响应为错误,再结合retry和delay实现间隔重试,最后在completion中处理最终结果:

func getDetialsById(url: URL) {
    self.responseModel = nil
    
    let maxRetries = 2 // 加上初始请求共3次
    let retryDelay: TimeInterval = 5
    
    self.service.details(url: url)
        .tryMap { model -> DetailModel in
            // 状态不是Paid时抛出错误,触发重试
            guard model.status == "Paid" else {
                throw NetworkCommunicationError.nonPaidStatus
            }
            return model
        }
        .retry(maxRetries) { error -> Bool in
            // 仅对自定义的nonPaidStatus错误进行重试
            guard case NetworkCommunicationError.nonPaidStatus = error else {
                return false
            }
            return true
        }
        .delay(for: .seconds(retryDelay), scheduler: DispatchQueue.global())
        .receive(on: DispatchQueue.main)
        .sink(receiveCompletion: { completion in
            switch completion {
            case .failure(let error):
                if case NetworkCommunicationError.nonPaidStatus = error {
                    // 重试耗尽仍未拿到Paid,设置showConfirmDetails为true
                    self.showConfirmDetails = true
                } else {
                    // 处理其他网络错误
                    self.handleErrorCase(error)
                }
            case .finished:
                // 成功拿到Paid状态
                self.showConfirmDetails = true
            }
        }, receiveValue: { value in
            self.responseModel = value
        })
        .store(in: &self.cancellebles)
}

关键说明

  • tryMap转换条件为错误:把不符合预期的成功响应转为错误,让Combine的重试逻辑能触发。
  • retry(_:if:)控制重试规则:限制仅对nonPaidStatus错误重试,最多重试2次(加上初始请求共3次)。
  • delay实现间隔:每次重试前等待5秒,使用全局队列避免阻塞主线程。
  • Completion最终处理:区分错误类型,重试耗尽后的非Paid错误直接设置showConfirmDetails = true,其他错误走原有错误处理逻辑。

如果原有NetworkCommunicationError无法扩展,可改用自定义错误枚举包装:

enum DetailError: Error {
    case network(NetworkCommunicationError)
    case nonPaidStatus
}

// 对应的管道修改
self.service.details(url: url)
    .mapError(DetailError.network)
    .tryMap { model -> DetailModel in
        guard model.status == "Paid" else {
            throw DetailError.nonPaidStatus
        }
        return model
    }
    .retry(maxRetries) { error -> Bool in
        guard case DetailError.nonPaidStatus = error else {
            return false
        }
        return true
    }
    .delay(for: .seconds(retryDelay), scheduler: DispatchQueue.global())
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { completion in
        switch completion {
        case .failure(let error):
            switch error {
            case .network(let netError):
                self.handleErrorCase(netError)
            case .nonPaidStatus:
                self.showConfirmDetails = true
            }
        case .finished:
            self.showConfirmDetails = true
        }
    }, receiveValue: { value in
        self.responseModel = value
    })
    .store(in: &self.cancellebles)

内容的提问来源于stack exchange,提问作者Varun Kashyap

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 19:12:26