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 shouldRetry(error), maxAttemptCount > attempt + 1 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.