Last active
January 17, 2021 03:49
-
-
Save winstondu/91da8a889ed4e42ef7b1c11951cd3edd to your computer and use it in GitHub Desktop.
Fix of memory leak on RepeatWithBehavior for RxSwiftExt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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