You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

104 lines
4.5 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. //
  2. // retryWithBehavior.swift
  3. // RxSwiftExt
  4. //
  5. // Created by Anton Efimenko on 17/07/16.
  6. // Copyright © 2016 RxSwift Community. All rights reserved.
  7. //
  8. import Foundation
  9. import RxSwift
  10. /**
  11. Specifies how observable sequence will be repeated in case of an error
  12. - Immediate: Will be immediatelly repeated specified number of times
  13. - Delayed: Will be repeated after specified delay specified number of times
  14. - ExponentialDelayed: Will be repeated specified number of times.
  15. Delay will be incremented by multiplier after each iteration (multiplier = 0.5 means 50% increment)
  16. - CustomTimerDelayed: Will be repeated specified number of times. Delay will be calculated by custom closure
  17. */
  18. public enum RepeatBehavior {
  19. case immediate (maxCount: UInt)
  20. case delayed (maxCount: UInt, time: Double)
  21. case exponentialDelayed (maxCount: UInt, initial: Double, multiplier: Double)
  22. case customTimerDelayed (maxCount: UInt, delayCalculator: (UInt) -> DispatchTimeInterval)
  23. }
  24. public typealias RetryPredicate = (Error) -> Bool
  25. extension RepeatBehavior {
  26. /**
  27. Extracts maxCount and calculates delay for current RepeatBehavior
  28. - parameter currentAttempt: Number of current attempt
  29. - returns: Tuple with maxCount and calculated delay for provided attempt
  30. */
  31. func calculateConditions(_ currentRepetition: UInt) -> (maxCount: UInt, delay: DispatchTimeInterval) {
  32. switch self {
  33. case .immediate(let max):
  34. // if Immediate, return 0.0 as delay
  35. return (maxCount: max, delay: .never)
  36. case .delayed(let max, let time):
  37. // return specified delay
  38. return (maxCount: max, delay: .milliseconds(Int(time * 1000)))
  39. case .exponentialDelayed(let max, let initial, let multiplier):
  40. // if it's first attempt, simply use initial delay, otherwise calculate delay
  41. let delay = currentRepetition == 1 ? initial : initial * pow(1 + multiplier, Double(currentRepetition - 1))
  42. return (maxCount: max, delay: .milliseconds(Int(delay * 1000)))
  43. case .customTimerDelayed(let max, let delayCalculator):
  44. // calculate delay using provided calculator
  45. return (maxCount: max, delay: delayCalculator(currentRepetition))
  46. }
  47. }
  48. }
  49. extension ObservableType {
  50. /**
  51. Repeats the source observable sequence using given behavior in case of an error or until it successfully terminated
  52. - parameter behavior: Behavior that will be used in case of an error
  53. - parameter scheduler: Schedular that will be used for delaying subscription after error
  54. - parameter shouldRetry: Custom optional closure for checking error (if returns true, repeat will be performed)
  55. - returns: Observable sequence that will be automatically repeat if error occurred
  56. */
  57. public func retry(_ behavior: RepeatBehavior, scheduler: SchedulerType = MainScheduler.instance, shouldRetry: RetryPredicate? = nil) -> Observable<Element> {
  58. return retry(1, behavior: behavior, scheduler: scheduler, shouldRetry: shouldRetry)
  59. }
  60. /**
  61. Repeats the source observable sequence using given behavior in case of an error or until it successfully terminated
  62. - parameter currentAttempt: Number of current attempt
  63. - parameter behavior: Behavior that will be used in case of an error
  64. - parameter scheduler: Schedular that will be used for delaying subscription after error
  65. - parameter shouldRetry: Custom optional closure for checking error (if returns true, repeat will be performed)
  66. - returns: Observable sequence that will be automatically repeat if error occurred
  67. */
  68. internal func retry(_ currentAttempt: UInt, behavior: RepeatBehavior, scheduler: SchedulerType = MainScheduler.instance, shouldRetry: RetryPredicate? = nil)
  69. -> Observable<Element> {
  70. guard currentAttempt > 0 else { return Observable.empty() }
  71. // calculate conditions for bahavior
  72. let conditions = behavior.calculateConditions(currentAttempt)
  73. return catchError { error -> Observable<Element> in
  74. // return error if exceeds maximum amount of retries
  75. guard conditions.maxCount > currentAttempt else { return Observable.error(error) }
  76. if let shouldRetry = shouldRetry, !shouldRetry(error) {
  77. // also return error if predicate says so
  78. return Observable.error(error)
  79. }
  80. guard conditions.delay != .never else {
  81. // if there is no delay, simply retry
  82. return self.retry(currentAttempt + 1, behavior: behavior, scheduler: scheduler, shouldRetry: shouldRetry)
  83. }
  84. // otherwise retry after specified delay
  85. return Observable<Void>.just(()).delaySubscription(conditions.delay, scheduler: scheduler).flatMapLatest {
  86. self.retry(currentAttempt + 1, behavior: behavior, scheduler: scheduler, shouldRetry: shouldRetry)
  87. }
  88. }
  89. }
  90. }