Skip to content

Instantly share code, notes, and snippets.

@kean
Last active September 20, 2023 20:21
Show Gist options
  • Star 57 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save kean/e2bc38106d19c249c04162714e7be321 to your computer and use it in GitHub Desktop.
Save kean/e2bc38106d19c249c04162714e7be321 to your computer and use it in GitHub Desktop.
Smart Auto Retry using RxSwift
// The MIT License (MIT)
//
// Copyright (c) 2017 Alexander Grebenyuk (github.com/kean).
import Foundation
import RxSwift
import RxCocoa
extension ObservableType {
/// Retries the source observable sequence on error using a provided retry
/// strategy.
/// - parameter maxAttemptCount: Maximum number of times to repeat the
/// sequence. `Int.max` by default.
/// - parameter didBecomeReachable: Trigger which is fired when network
/// connection becomes reachable.
/// - parameter shouldRetry: Always retruns `true` by default.
func retry(_ maxAttemptCount: Int = Int.max,
delay: DelayOptions,
didBecomeReachable: Signal<Void> = Reachability.shared.didBecomeReachable,
shouldRetry: @escaping (Error) -> Bool = { _ in true }) -> Observable<E> {
return retryWhen { (errors: Observable<Error>) in
return errors.enumerated().flatMap { attempt, error -> Observable<Void> in
guard maxAttemptCount > attempt + 1, shouldRetry(error) else {
return .error(error)
}
let timer = Observable<Int>.timer(
RxTimeInterval(delay.make(attempt + 1)),
scheduler: MainScheduler.instance
).map { _ in () } // cast to Observable<Void>
return Observable.merge(timer, didBecomeReachable.asObservable())
}
}
}
}
enum DelayOptions {
case immediate()
case constant(time: Double)
case exponential(initial: Double, multiplier: Double, maxDelay: Double)
case custom(closure: (Int) -> Double)
}
extension DelayOptions {
func make(_ attempt: Int) -> Double {
switch self {
case .immediate: return 0.0
case .constant(let time): return time
case .exponential(let initial, let multiplier, let maxDelay):
// if it's first attempt, simply use initial delay, otherwise calculate delay
let delay = attempt == 1 ? initial : initial * pow(multiplier, Double(attempt - 1))
return min(maxDelay, delay)
case .custom(let closure): return closure(attempt)
}
}
}
// The MIT License (MIT)
//
// Copyright (c) 2017 Alexander Grebenyuk (github.com/kean).
import Foundation
import Alamofire
import RxSwift
import RxCocoa
final class Reachability {
static var shared: Reachability {
return AppContainer.shared.reachability
}
/// Monitors general network reachability.
let reachability = NetworkReachabilityManager()
var didBecomeReachable: Signal<Void> { return _didBecomeReachable.asSignal() }
private let _didBecomeReachable = PublishRelay<Void>()
init() {
self.isReachable = _isReachable.asDriver()
if let reachability = self.reachability {
reachability.listener = { [weak self] in
self?.update($0)
}
reachability.startListening()
}
}
private func update(_ status: NetworkReachabilityManager.NetworkReachabilityStatus) {
if case .reachable = status {
_didBecomeReachable.accept(())
}
}
}
@IAmTheEye
Copy link

nice extension - but the check for max attempt count should be maxAttemptCount >= attempt + 1 I think

@netspencer
Copy link

thank you for this!

@winstondu
Copy link

Why is it errors.enumerated().flatMap vs errors.enumerated().flatMapLatest()?

@markcleonard
Copy link

Thanks a lot!

Small comment: Perhaps better to check the maxAttemptCount before shouldRetry.

@svyatoslav-zubrin
Copy link

nice one, thanks

@kean
Copy link
Author

kean commented Jan 10, 2021

Thanks a lot!

Small comment: Perhaps better to check the maxAttemptCount before shouldRetry.

Yeah, that's a good idea, thanks. Updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment