Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxSwift. Execute separate Observables sequently

I'm trying to achieve my Observables to execute only when previous Observable has completed. I can't use flatMap, because subscriptions can be called from different places, and this Observables is not connected with each other. To be specific: I have my CollectionView loading more content from server and 2 seconds after that user clicks "Send comment" button while CollectionView is still loading its batch. So I want to wait until CollectionView update completes and only then execute my comment's posting request. I created a class named ObservableQueue and it's working just fine. But I need to know if it has issues like memory leaks, dead locks or maybe I just missing something. Here it is:

extension CompositeDisposable {

    @discardableResult
    func insert(disposeAction: @escaping () -> ()) -> DisposeKey? {
        return insert(Disposables.create(with: disposeAction))
    }

}

class ObservableQueue {

    private let lock = NSRecursiveLock()
    private let relay = BehaviorRelay(value: 0)
    private let scheduler = SerialDispatchQueueScheduler(internalSerialQueueName: "ObservableQueue.scheduler")

    func enqueue<T>(_ observable: Observable<T>) -> Observable<T> {
        return Observable.create({ observer -> Disposable in
            let disposable = CompositeDisposable()

            let relayDisposable = self
                .relay
                .observeOn(self.scheduler)
                .filter({ value -> Bool in
                    if value > 0 {
                        return false
                    }

                    self.lock.lock(); defer { self.lock.unlock() }

                    if self.relay.value > 0 {
                        return false
                    }

                    self.relay.accept(self.relay.value + 1)

                    disposable.insert {
                        self.lock.lock(); defer { self.lock.unlock() }
                        self.relay.accept(self.relay.value - 1)
                    }

                    return true
                })
                .take(1)
                .flatMapLatest { _ in observable }
                .subscribe { observer.on($0) }

            _ = disposable.insert(relayDisposable)

            return disposable
        })
    }

}

And then I can use it like this:

let queue = ObservableQueue()

...

// first observable
let observable1 = Observable
    .just(0)
    .delay(5, scheduler: MainScheduler.instance)

queue
    .enqueue(observable1)
    .subscribe(onNext: { _ in
        print("here1")
     })
    .disposed(by: rx.disposeBag)

// second observable
let observable2 = Observable
    .just(0)
    .delay(5, scheduler: MainScheduler.instance)

queue
    .enqueue(observable2)
    .subscribe(onNext: { _ in
        print("here2")
    })
    .disposed(by: rx.disposeBag)

// third observable
let observable3 = Observable
    .just(0)
    .delay(5, scheduler: MainScheduler.instance)

queue
    .enqueue(observable3)
    .subscribe(onNext: { _ in
        print("here3")
    })
    .disposed(by: rx.disposeBag)
like image 225
KY1VSTAR Avatar asked Jul 29 '26 23:07

KY1VSTAR


1 Answers

CLGeocoder has the same issue. According to the documentation, you can't call one of the geocoder methods while it's working on a previous request so very much like what you are trying to do. In this gist (https://gist.github.com/danielt1263/64bda2a32c18b8c28e1e22085a05df5a), you will find that I make the observable calls on a background thread and protect the job with semaphore. That's the key, you need a semaphore, not a lock.

Something like this should work for you:

class ObservableQueue {

    private let semaphore = DispatchSemaphore(value: 1)
    private let scheduler = ConcurrentDispatchQueueScheduler(qos: .userInitiated)

    func enqueue<T>(_ observable: Observable<T>) -> Observable<T> {
        let _semaphore = semaphore // To avoid the use of self in the block below
        return Observable.create { observer in
            _semaphore.wait()
            let disposable = observable.subscribe { event in
                switch event {
                case .next:
                    observer.on(event)
                case .error, .completed:
                    observer.on(event)
                }
            }
            return Disposables.create {
                disposable.dispose()
                _semaphore.signal()
            }
        }
        .subscribeOn(scheduler)
    }
}
like image 68
Daniel T. Avatar answered Jul 31 '26 15:07

Daniel T.