首页 > 解决方案 > How to retry failed web service calls with time delay in ios app

问题描述

I am developing an IOS app and as per the requirement I need to retry all failed api for 3 times with 3 seconds delay. In order to do that I have added an extension to a Publisher as follows, but there is one small problem with this code, after the last attempt if I get a failed response then it takes 3 seconds to execute the expected failure code. I need to show failure message as soon as last attempt fails, is there any way to handle this?

public extension Publisher {
    func retryWithDelay<S>(
        retries: Int,
        delay: S.SchedulerTimeType.Stride,
        scheduler: S
    ) -> AnyPublisher<Output, Failure> where S: Scheduler {
        
        self
            .delayIfFailure(for: delay, scheduler: scheduler)
            .retry(retries)
            .eraseToAnyPublisher()
    }

    private func delayIfFailure<S>(
        for delay: S.SchedulerTimeType.Stride,
        scheduler: S
    ) -> AnyPublisher<Output, Failure> where S: Scheduler {
        self.catch { error in
            Future { completion in
                scheduler.schedule(after: scheduler.now.advanced(by: delay)) {
                    completion(.failure(error))
                }
            }
        }
        .eraseToAnyPublisher()
    }
}

标签: iosswiftcombine

解决方案


It would probably be best to create a custom Publisher, with the idea being that you'd keep track of the number of failures, and either return a delayed publisher (that fails) under normal conditions, or a non-delayed publisher for the last retry.

struct RetryWithDelay<Upstream: Publisher, S: Scheduler>: Publisher {
    
   typealias Output = Upstream.Output
   typealias Failure = Upstream.Failure
    
   let upstream: Upstream
   let retries: Int
   let interval: S.SchedulerTimeType.Stride
   let scheduler: S
    
   func receive<Sub>(subscriber: Sub) 
          where Sub : Subscriber, 
                Upstream.Failure == Sub.Failure, 
                Upstream.Output == Sub.Input {

      var retries = self.retries
      let p = upstream
         .catch { err -> AnyPublisher<Output, Failure> in
            retries -= 1
            return Fail(error: err)
               .delay(for: retries > 0 ? interval : .zero, scheduler: scheduler)
               .eraseToAnyPublisher()
         }
         .retry(self.retries)

      p.subscribe(subscriber)
   }
}

For convenience, you can create an operator, like you did in the question:

public extension Publisher {
    func retryWithDelay<S>(
        retries: Int,
        delay: S.SchedulerTimeType.Stride,
        scheduler: S
    ) -> RetryWithDelay<Self, S> where S: Scheduler {
        
        RetryWithDelay(upstream: self, 
                       retries: retries, delay: delay, scheduler: scheduler)
    }
}

推荐阅读