Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

@IAmTheEye IAmTheEye commented Mar 5, 2019

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

@netspencer

This comment has been minimized.

Copy link

@netspencer netspencer commented Mar 18, 2019

thank you for this!

@winstondu

This comment has been minimized.

Copy link

@winstondu winstondu commented May 18, 2020

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

@markcleonard

This comment has been minimized.

Copy link

@markcleonard markcleonard commented Jul 30, 2020

Thanks a lot!

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

@svyatoslav-zubrin

This comment has been minimized.

Copy link

@svyatoslav-zubrin svyatoslav-zubrin commented Sep 20, 2020

nice one, thanks

@kean

This comment has been minimized.

Copy link
Owner Author

@kean 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