Skip to content

Instantly share code, notes, and snippets.

@winstondu
Last active January 17, 2021 03:49
Show Gist options
  • Save winstondu/91da8a889ed4e42ef7b1c11951cd3edd to your computer and use it in GitHub Desktop.
Save winstondu/91da8a889ed4e42ef7b1c11951cd3edd to your computer and use it in GitHub Desktop.
Fix of memory leak on RepeatWithBehavior for RxSwiftExt
//
// Observable+RepeatWhenFixed.swift
// Backbone
//
// Created by Winston Du on 1/16/21.
import Foundation
import RxSwift
import RxSwiftExt
// a fix to https://github.com/RxSwiftCommunity/RxSwiftExt/issues/250
extension RepeatBehavior {
/**
Extracts maxCount and calculates delay for current RepeatBehavior
- parameter currentAttempt: Number of current attempt
- returns: Tuple with maxCount and calculated delay for provided attempt
*/
func calculateConditions(_ currentRepetition: UInt) -> (maxCount: UInt, delay: DispatchTimeInterval) {
switch self {
case .immediate(let max):
// if Immediate, return 0.0 as delay
return (maxCount: max, delay: .never)
case .delayed(let max, let time):
// return specified delay
return (maxCount: max, delay: .milliseconds(Int(time * 1000)))
case .exponentialDelayed(let max, let initial, let multiplier):
// if it's first attempt, simply use initial delay, otherwise calculate delay
let delay = currentRepetition == 1 ? initial : initial * pow(1 + multiplier, Double(currentRepetition - 1))
return (maxCount: max, delay: .milliseconds(Int(delay * 1000)))
case .customTimerDelayed(let max, let delayCalculator):
// calculate delay using provided calculator
return (maxCount: max, delay: delayCalculator(currentRepetition))
}
}
}
public typealias RepeatPredicate = () -> Bool
/*
Uses RepeatBehavior defined in retryWithBehavior
*/
/** Dummy error to use with catchError to restart the observable */
private enum RepeatError: Error {
case catchable
}
extension ObservableType {
/**
Repeats the source observable sequence using given behavior when it completes
- parameter behavior: Behavior that will be used when the observable completes
- parameter scheduler: Schedular that will be used for delaying subscription after error
- parameter shouldRepeat: Custom optional closure for decided whether the observable should repeat another round
- returns: Observable sequence that will be automatically repeat when it completes
*/
public func repeatWithBehaviorFixed(_ behavior: RepeatBehavior, scheduler: SchedulerType = MainScheduler.instance, shouldRepeat: RepeatPredicate? = nil) -> Observable<Element> {
return repeatWithBehaviorFixed(1, behavior: behavior, scheduler: scheduler, shouldRepeat: shouldRepeat)
}
/**
Repeats the source observable sequence using given behavior when it completes
- parameter currentRepeat: Number of the current repetition
- parameter behavior: Behavior that will be used in case of completion
- parameter scheduler: Schedular that will be used for delaying subscription after error
- parameter shouldRepeat: Custom optional closure for decided whether the observable should repeat another round
- returns: Observable sequence that will be automatically repeat when it completes
*/
internal func repeatWithBehaviorFixed(_ currentRepeat: UInt, behavior: RepeatBehavior, scheduler: SchedulerType = MainScheduler.instance, shouldRepeat: RepeatPredicate? = nil)
-> Observable<Element> {
guard currentRepeat > 0 else { return Observable.empty() }
var currentRepeat: UInt = 0
return
concat(Observable.error(RepeatError.catchable))
.retryWhen { (errors: Observable<Error>) in
return errors
.flatMapLatest { error -> Observable<Void> in
//if observable errors, forward the error
guard error is RepeatError else {
return Observable.error(error)
}
currentRepeat += 1
// calculate conditions for bahavior
let conditions = behavior.calculateConditions(currentRepeat)
//repeat
guard conditions.maxCount > currentRepeat else { return Observable.empty() }
if let shouldRepeat = shouldRepeat, !shouldRepeat() {
// also return error if predicate says so
return Observable.empty()
}
guard conditions.delay != .never else {
// if there is no delay, simply retry
return Observable.just(())
}
// otherwise retry after specified delay
return Observable<Void>.just(()).delaySubscription(conditions.delay, scheduler: scheduler)
.map { _ in
()
} // cast to Observable<Void>
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment