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)
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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With