RxSwift - Is it ok to dispose sequence from its own subscribe?
So let's say we have a function that ends with callback and we wanna do something if the callback is called.
It can be any function, I will use function that asks user for cofirmation for this example.
func userConfirmed(callback: () -> Void) {
// Here we would show confirmation dialog and if user clicks "Confirm", the callback is called, otherwise function does nothing
}
Now we have a Rx sequence and we want to call our function for every element of a sequence. If user confirms the action (callback is called), sequence should be disposed so the confirmed action cannot be performed multiple times, otherwise sequence should continue.
I would use DisposeBag like this, which seems wrong:
var db = DisposeBag()
button.rx.tap.subscribe(onNext: {
userConfirmed {
db = DisposeBag()
// Do stuff
}
}).disposed(by: db)
Alternate solution I can think of is using flatMap
, Observable.create
and take
, which should not cause any issues, but seems really complicated for such a simple task.
button.tap
.flatMap {
return Observable<Void>.create { event in
userConfirmed {
event.onNext(())
event.onCompleted()
}
return Disposables.create()
}
}
.take(1)
.subscribe(onNext: {
// Do stuff
}
.disposed(by: db)
Questions
- Is it ok to dispose a sequence using DisposeBag directly in subscribe like that or do I have to use the alternate solution? (And why?)
- Is there any better solution for this than the two presented?
I know that this example can be solved by disabling/hiding the button after user confirms it, but that is not the point of the question.
The thing that makes Lego bricks so easy to reconfigure and reuse in lots of contexts is that they all conform to the same interface (they all have the studs on top in the same configuration.) Imagine how much of a pain it would be to build something if only some blocks had the square pattern of studs while others had a hex pattern and still others used some other pattern...
The iOS ecosystem has something like 9 different patterns for pushing data around (listed below.) Much of the complexity inherent in writing an app comes from having to integrate all these various patterns into a coherent whole. One of the things that makes Rx so pleasant to work with is that it provides a way to make all of these conform to a single, simple, and consistent pattern. Much like with Legos, having all of your sub-systems conform to a single pattern makes your code much less painful. Easier to build and reconfigure as needed.
To this end, it may seem like a pain to convert your callback function into a function that returns an Observable, but by doing so, your function becomes part of the overall ecosystem, and as such its utility is improved.
All this is to say that the Observable.create
route is, in my opinion at least, the best solution to this question. I would move it into it's own function though rather than doing the Observable.create
within the flatMap. Doing so makes the call site much cleaner:
func userConfirmed() -> Observable<Void> {
Observable.create { observer in
userConfirmed {
observer.onNext(())
observer.onCompleted()
}
return Disposables.create()
}
}
Better still would be to use a library that already wraps the functionality you need into an Observable. There is my CLE library as well as RxModal that both have tools for presenting (Alert) view controllers and emitting values from them based on user choice.
Okay... The above is me making the argument for why you should use the "seems really complicated for such a simple task" method, but if it's not your thing and you want to mix and match systems (which makes your code more complex) then your disposeBag idea is fine. It feels a bit odd using a bag for a single disposable though...
As promised, here is a list of all the ways Apple makes us push data:
- closures
- target/action (UIControl aka IBAction)
- delegates
- notifications (NotificationCenter)
- KVO
- setting variables (did set/will set)
- sub-classing abstract base classes
- async/await
- publishers