Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 延迟后是否重试?_Ios_Swift_Combine - Fatal编程技术网

Ios 延迟后是否重试?

Ios 延迟后是否重试?,ios,swift,combine,Ios,Swift,Combine,我了解了如何使用。请直接重试,以便在出现错误后重新订阅,如下所示: URLSession.shared.dataTaskPublisher(for:url) .retry(3) 但这似乎非常简单。如果我认为如果我等一会儿,这个错误可能会消失呢?我可以插入一个.delay操作符,但是即使没有错误,延迟也会运行。而且似乎没有一种方法可以有条件地应用运算符(即,仅当出现错误时) 我知道如何通过从头编写RetryWithDelay操作符来解决这个问题,事实上,这样的操作符是由第

我了解了如何使用
。请直接重试
,以便在出现错误后重新订阅,如下所示:

    URLSession.shared.dataTaskPublisher(for:url)
        .retry(3)
但这似乎非常简单。如果我认为如果我等一会儿,这个错误可能会消失呢?我可以插入一个
.delay
操作符,但是即使没有错误,延迟也会运行。而且似乎没有一种方法可以有条件地应用运算符(即,仅当出现错误时)

我知道如何通过从头编写RetryWithDelay操作符来解决这个问题,事实上,这样的操作符是由第三方编写的。但是有没有一种方法可以说“如果有错误就延迟”,仅仅使用我们得到的操作符


我的想法是我可以使用
.catch
,因为它的函数只有在出现错误时才会运行。但是函数需要返回一个发布者,我们将使用哪个发布者?如果我们返回
somePublisher.delay(…)
,然后返回
.retry
,我们将应用
。retry
到错误的发布者,不是吗?

前一段时间,这是一个关于项目repo的话题-整个线程:

长话短说,我们举了一个例子,我认为它符合你的要求,尽管它确实使用了
catch

让resultPublisher=upstreamPublisher.catch{error->AnyPublisher在
返回发布者。延迟(上游:上游发布者,
间隔时间:3,
公差:1,
调度程序:DispatchQueue.global())
//将重试移到此块中可减少重复请求的数量
//实际上,这是原始请求,这里的“重试(2)”将起作用
//对以其他方式启动的一次性发布服务器进行两次额外重试
//就在上面的'publisher.Delay()'。只要用Delay启动此发布服务器,就可以
//一个额外的请求,因此请求总数最终为4(假设所有
//失败)。但是,如果原始请求
//这是成功的。
.重试(2)
.删除任何发布者()
}
这是指a,这基本上就是你所描述的(但不是你所问的)

在该线程中提供了一个变体,作为一个可能也很有趣的扩展:

扩展发布程序{
func retryWithDelay()
->Catch,其中T==Self.Output,E==Self.Failure
{
在中返回self.catch{error->AnyPublisher
返回发布者。延迟(
上游:自我,
间隔时间:3,
公差:1,
计划程序:DispatchQueue.global())。重试(2)。删除任何发布服务器()
}
}
}

使用
。catch
确实是答案。我们只需引用数据任务发布者,并将该引用用作两个管道的头—进行初始联网的外部管道和由
.catch
函数生成的内部管道

让我们从创建数据任务发布器开始,然后停止:

现在我可以形成管道的头部:

let head = pub.catch {_ in pub.delay(for: 3, scheduler: DispatchQueue.main)}
    .retry(3)
那就够了
head
现在是一个管道,仅在出现错误时插入延迟操作符。然后,我们可以根据
head
继续形成管道的其余部分


注意,我们确实改变了出版商;如果出现故障并且运行了
catch
函数,则
pub
成为发布者,它是
.delay
的上游,取代了我们最初使用的
pub
。但是,它们是同一个对象(因为我说的是共享),所以这是一个没有区别的区别。

我在公认的答案中发现了一些与实现相关的怪癖

  • 首先,前两次尝试将立即启动,因为第一次延迟仅在第二次尝试后生效

  • 其次,如果任何一次重试尝试成功,输出值也将延迟,这似乎是不必要的

  • 第三,扩展不够灵活,不允许用户决定将重试尝试分派给哪个调度程序

经过反复修补,我最终得到了如下解决方案:

    URLSession.shared.dataTaskPublisher(for:url)
        .retry(3)
公共扩展发布程序{
/**
创建一个新发布服务器,该发布服务器将在出现故障时重试上游发布服务器提供的次数,并在重试尝试之间提供延迟。
如果上游发布服务器第一次成功,则此操作将被绕过,并正常进行。
-参数:
-重试次数:重试上游发布服务器的次数。
-延迟:重试尝试之间的延迟(秒)。
-调度程序:调度延迟事件的调度程序。
-返回:一个新的发布服务器,失败后将延迟重试上游发布服务器。
~~~
让url=url(字符串:https://api.myService.com")!
URLSession.shared.dataTaskPublisher(用于:url)
.retryWithDelay(重试次数:4次,延迟时间:5次,计划程序:DispatchQueue.global())
.sink{在中完成
交换完成{
案例。完成:
print(“Success我记得这个库有一个非常好的自定义重试+延迟操作符的实现,有很多选项(线性和指数延迟,加上一个提供自定义闭包的选项),我尝试在Combine中重新创建它。最初的实现是

/**
提供将使用的重试行为—重试次数和两次后续重试之间的延迟。
-`.immediate`:它将立即重试指定的重试次数
-`.delayed`:它将重试指定的重试次数,在每次重试之间添加一个固定的延迟
-`.ExponentalDelayed`:它将重试指定的重试次数。
每次迭代后,延迟将由提供的乘数递增
(`multiplier=0.5`对应于
/**
 Provides the retry behavior that will be used - the number of retries and the delay between two subsequent retries.
 - `.immediate`: It will immediatelly retry for the specified retry count
 - `.delayed`: It will retry for the specified retry count, adding a fixed delay between each retry
 - `.exponentialDelayed`: It will retry for the specified retry count.
 The delay will be incremented by the provided multiplier after each iteration
 (`multiplier = 0.5` corresponds to 50% increase in time between each retry)
 - `.custom`: It will retry for the specified retry count. The delay will be calculated by the provided custom closure.
 The closure's argument is the current retry
 */
enum RetryBehavior<S> where S: Scheduler {
  case immediate(retries: UInt)
  case delayed(retries: UInt, time: TimeInterval)
  case exponentialDelayed(retries: UInt, initial: TimeInterval, multiplier: Double)
  case custom(retries: UInt, delayCalculator: (UInt) -> TimeInterval)
}

fileprivate extension RetryBehavior {
  
  func calculateConditions(_ currentRetry: UInt) -> (maxRetries: UInt, delay: S.SchedulerTimeType.Stride) {
    
    switch self {
    case let .immediate(retries):
      // If immediate, returns 0.0 for delay
      return (maxRetries: retries, delay: .zero)
    case let .delayed(retries, time):
      // Returns the fixed delay specified by the user
      return (maxRetries: retries, delay: .seconds(time))
    case let .exponentialDelayed(retries, initial, multiplier):
      // If it is the first retry the initial delay is used, otherwise it is calculated
      let delay = currentRetry == 1 ? initial : initial * pow(1 + multiplier, Double(currentRetry - 1))
      return (maxRetries: retries, delay: .seconds(delay))
    case let .custom(retries, delayCalculator):
      // Calculates the delay with the custom calculator
      return (maxRetries: retries, delay: .seconds(delayCalculator(currentRetry)))
    }
    
  }
  
}

public typealias RetryPredicate = (Error) -> Bool

extension Publisher {
  /**
   Retries the failed upstream publisher using the given retry behavior.
   - parameter behavior: The retry behavior that will be used in case of an error.
   - parameter shouldRetry: An optional custom closure which uses the downstream error to determine
   if the publisher should retry.
   - parameter tolerance: The allowed tolerance in firing delayed events.
   - parameter scheduler: The scheduler that will be used for delaying the retry.
   - parameter options: Options relevant to the scheduler’s behavior.
   - returns: A publisher that attempts to recreate its subscription to a failed upstream publisher.
   */
  func retry<S>(
    _ behavior: RetryBehavior<S>,
    shouldRetry: RetryPredicate? = nil,
    tolerance: S.SchedulerTimeType.Stride? = nil,
    scheduler: S,
    options: S.SchedulerOptions? = nil
  ) -> AnyPublisher<Output, Failure> where S: Scheduler {
    return retry(
      1,
      behavior: behavior,
      shouldRetry: shouldRetry,
      tolerance: tolerance,
      scheduler: scheduler,
      options: options
    )
  }
  
  private func retry<S>(
    _ currentAttempt: UInt,
    behavior: RetryBehavior<S>,
    shouldRetry: RetryPredicate? = nil,
    tolerance: S.SchedulerTimeType.Stride? = nil,
    scheduler: S,
    options: S.SchedulerOptions? = nil
  ) -> AnyPublisher<Output, Failure> where S: Scheduler {
    
    // This shouldn't happen, in case it does we finish immediately
    guard currentAttempt > 0 else { return Empty<Output, Failure>().eraseToAnyPublisher() }
    
    // Calculate the retry conditions
    let conditions = behavior.calculateConditions(currentAttempt)
    
    return self.catch { error -> AnyPublisher<Output, Failure> in
      
      // If we exceed the maximum retries we return the error
      guard currentAttempt <= conditions.maxRetries else {
        return Fail(error: error).eraseToAnyPublisher()
      }
      
      if let shouldRetry = shouldRetry, shouldRetry(error) == false {
        // If the shouldRetry predicate returns false we also return the error
        return Fail(error: error).eraseToAnyPublisher()
      }
      
      guard conditions.delay != .zero else {
        // If there is no delay, we retry immediately
        return self.retry(
          currentAttempt + 1,
          behavior: behavior,
          shouldRetry: shouldRetry,
          tolerance: tolerance,
          scheduler: scheduler,
          options: options
        )
        .eraseToAnyPublisher()
      }
      
      // We retry after the specified delay
      return Just(()).delay(for: conditions.delay, tolerance: tolerance, scheduler: scheduler, options: options).flatMap {
        return self.retry(
          currentAttempt + 1,
          behavior: behavior,
          shouldRetry: shouldRetry,
          tolerance: tolerance,
          scheduler: scheduler,
          options: options
        )
        .eraseToAnyPublisher()
      }
      .eraseToAnyPublisher()
    }
    .eraseToAnyPublisher()
  }
  
}