RxSwift, Sometimes .disposed get called without calling .subscribe - dispose

In rx code, .disposed get called without any doing job like flatmap, subscribe. This happens only when I build my app at first time.
Does anybody knows what happens here?
This is my code
HTTP.getSomething()
.flatMap { (list) -> Single<Void> in
return HTTP.getList(withList: list)
}
.subscribe(onSuccess: { (storeList) in
log.debug("Finish!!!")
}, onError: { [weak self] (error) in
self?.presentAlert(error: error)
})
.disposed(by: self.disposeBag)

The only way the code you presented can possibly be disposed without attempting the work inside of the flatMap is if getSomething emits a completed without emitting a value, or if it emits an error, or if the disposeBag is deleted. One of those three things is happening.
Since you say it only happens on first build, I suspect that getSomething is trying to make a network call before it has all the info it needs which is causing it to emit an error.

Related

Swift Combine publishers vs completion handler and when to cancel

I know in general a publisher is more powerful than a closure, however I want to ask and discuss a specific example:
func getNotificationSettingsPublisher() -> AnyPublisher<UNNotificationSettings, Never> {
let notificationSettingsFuture = Future<UNNotificationSettings, Never> { (promise) in
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
promise(.success(settings))
}
}
return notificationSettingsFuture.eraseToAnyPublisher()
}
I think this is a valid example of a Future publisher and it could be used here instead of using a completion handler. Let's do something with it:
func test() {
getNotificationSettingsPublisher().sink { (notificationSettings) in
// Do something here
}
}
This works, however it will tell me that the result of sink (AnyCancellable) is unused. So whenever I try to get a value, I need to either store the cancellable or assign it until I get a value.
Is there something like sinkOnce or an auto destroy of cancellables? Sometimes I don't need tasks to the cancelled. I could however do this:
func test() {
self.cancellable = getNotificationSettingsPublisher().sink { [weak self] (notificationSettings) in
self?.cancellable?.cancel()
self?.cancellable = nil
}
}
So once I receive a value, I cancel the subscription. (I could do the same in the completion closure of sink I guess).
What's the correct way of doing so? Because if I use a closure, it will be called as many times as the function is called, and if it is called only once, then I don't need to cancel anything.
Would you say normal completion handlers could be replaced by Combine and if so, how would you handle receiving one value and then cancelling?
Last but not least, the completion is called, do I still need to cancel the subscription? I at least need to update the cancellable and set it to nil right? I assume storing subscriptions in a set is for long running subscriptions, but what about single value subscriptions?
Thanks
Instead of using the .sink operator, you can use the Sink subscriber directly. That way you don't receive an AnyCancellable that you need to save. When the publisher completes the subscription, Combine cleans everything up.
func test() {
getNotificationSettingsPublisher()
.subscribe(Subscribers.Sink(
receiveCompletion: { _ in },
receiveValue: ({
print("value: \($0)")
})
))
}

RxSwift: How to respond to a series of notifications?

Suppose I have two notifications coming one after another. I need to wait for work to complete from 1st notification and only then fire the work from 2nd notification. For now, I tried to schedule sequences to a serial scheduler, but it doesn't work as expected, it seems that I'm missing something.
NotificationCenter.default.rx.notification(.notification1)
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] in
self?.doSomeAsynchWork() //Fires another subscription, kind of ugly
})
.disposed(by: disposeBag)
NotificationCenter.default.rx.notification(.notification2)
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] in
self?.doSomeWork() //This should only be executed after doSomeAsynchWork() is done
})
.disposed(by: disposeBag)
I was expecting work to be done in a serial manner, but that's not the case, my guess is that doSomeAsynchWork() is, well, asynchronous and doSomeWork() fires right after. But can I somehow wait for asynchronous work to complete? Any help is appreciated
UPD: notification2 may or may not arrive, so they are kind of independent of each other. Also, notification1 may or may not arrive, it's just different use cases in the app. But when both notification1 and notification2 are present, I need to wait for doSomeAsynchWork() to finish
The flow is as follows:
User taps to block some element in the list, which is only allowed for a signed-in user
User gets redirected to a sign-in screen
User sings in and then, notification1 fires
We continue to block that element now that we're signed in
Notification2 fires
The problem is when notification1 fired, we need to reload the screen, so that logic comes to doSomeAsynchWork(). On top of that, we're getting the "delete element" notification and we're trying to locate the element which is not there yet, so we're kind of stuck with an inconsistent state, where the element's blocked, but still present on a screen
The difficulty is that we can sign-in without blocking element and we can block element without the need of signing-in in (because we are already signed-in for example)
Based on the flow you described in your update, I would expect to see something like this:
func example(tapElement: Observable<ID>, isLoggedIn: Observable<Bool>, presentLogin: Observable<Void>) {
tapElement
.withLatestFrom(isLoggedIn) { (id: $0, isLoggedIn: $1) }
.flatMapFirst { id, isLoggedIn in
isLoggedIn ? Observable.just(id) : presentLogin.map { id }
}
.subscribe(onNext: { id in
blockElement(id: id)
})
}
I don't see any reason to have all the notifications in the first place.
Old Answer
I would have doSomeAsynchWork() return an Observable<Void> which emits an event with the async work is complete. Then I could:
NotificationCenter.default.rx.notification(.notification1)
.flatMap { doSomeAsynchWork() }
.subscribe(onNext: { doSomeWork() }
Another option would be to have doSomeAsynchWork() return a Completable, then you would do something like:
NotificationCenter.default.rx.notification(.notification1)
.flatMap { doSomeAsynchWork() }
.subscribe(onCompleted: { doSomeWork() }

What's the difference between .sink and Subscribers.Sink?

I want to do an asynchronous job with Future.
But the below .sink() closures never get called.
It seems that the instance of Future was released right after it was called.
Future<Int, Never> { promise in
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
promise(.success(1))
}
}
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { completion in
print(completion)
}, receiveValue: {
print($0)
})
So I replaced .sink() closures to .subscribe(Subscribers.Sink()) like below. It works fine.
But the problem is I don't understand why it works fine. :(
It looks the same to me.
What is the difference between these two codes? And when can I use .sink(), and when can I not?
Future<Int, Never> { promise in
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
promise(.success(1))
}
}
.receive(on: DispatchQueue.main)
.subscribe(Subscribers.Sink(receiveCompletion: { completion in
print(completion)
}, receiveValue: {
print($0)
}))
Thanks in advance.
The .sink operator does three things:
It creates a Subscribers.Sink using the two closures you pass it.
It calls subscribe on the upstream Publisher, passing the Sink it created.
It creates an AnyCancellable that, when destroyed, cancels the Sink. It returns a reference to this AnyCancellable.
AnyCancellable is a reference-counted object. When the last reference to the AnyCancellable is destroyed, the AnyCancellable itself is destroyed. At that time, it calls its own cancel method.
In your first example, you are not saving the AnyCancellable returned by .sink. So Swift destroys it immediately, which means it cancels the subscription immediately. One second later, your asyncAfter closure calls promise, but the subscription has already been cancelled, so your receiveValue closure is not called.
In your second example, since you are creating the Subscribers.Sink object and passing it to subscribe yourself, no AnyCancellable is created to wrap the Sink. So nothing automatically destroys the subscription. One second later, the asyncAfter closure calls promise. Since the subscription wasn't destroyed, it still exists, so your receiveValue closure is called, and then your receiveCompletion closure is called.
So this is actually a very interesting use of Subscribers.Sink instead of the .sink operator. With .sink, you must save the returned AnyCancellable, else the subscription is cancelled immediately. But by using Subscribers.Sink directly, you create a subscription that lasts until it is completed, and you don't have to save anything. And when the subscription completes (with either .finished or .failure), the Sink discards the Subscription, which breaks the retain cycle that was keeping it alive, so the Sink and the Subscription are also destroyed, leaving no memory leaks.

Is there a way to throw errors from asynchronous closures in Swift 3?

I’m executing some functions in a test asynchronously using a DispatchQueue like this:
let queue: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated)
let group: DispatchGroup = DispatchGroup()
func execute(argument: someArg) throws {
group.enter()
queue.async {
do {
// Do stuff here
group.leave()
} catch {
Log.info(“Something went wrong")
}
}
group.wait()
}
Sometimes the code inside the do block can throw errors, that I have to catch later on. Since I’m developing a test, I want it to fail, if the code inside the do block throws an error.
Is there a way to throw an error, without catching it inside the queue.async call?
You cannot throw an error, but you can return an error:
First, you need to make your calling function asynchronous as well:
func execute(argument: someArg, completion: #escaping (Value?, Error?)->()) {
queue.async {
do {
// compute value here:
...
completion(value, nil)
} catch {
completion(nil, error)
}
}
}
The completion handler takes a parameter which we could say is a "Result" containing either the value or an error. Here, what we have is a tuple (Value?, Error?), where Value is the type which is calculated by the task. But instead, you could leverage a more handy Swift Enum for this, e.g. Result<T> or Try<T> (you might want to the search the web).
Then, you use it as follows:
execute(argument: "Some string") { value, error in
guard error == nil else {
// handle error case
}
guard let value = value else {
fatalError("value is nil") // should never happen!
}
// do something with the value
...
}
Some rules that might help:
If a function calls an asynchronous function internally, it inevitable becomes an asynchronous function as well. *)
An asynchronous function should have a completion handler (otherwise, it's some sort of "fire and forget").
The completion handler must be called, no matter what.
The completion handler must be called asynchronously (with respect the the caller)
The completion handler should be called on a private execution context (aka dispatch queue) unless the function has a parameter specifying where to execute the completion handler. Never use the main thread or main dispatch queue - unless you explicitly state that fact in the docs or you intentionally want to risk dead-locks.
*) You can force it to make it synchronous using semaphores which block the calling thread. But this is inefficient and really rarely needed.
Well, you might conclude, that this looks somewhat cumbersome. Fortunately, there's help - you might look for Future or Promise which can nicely wrap this and make the code more concise and more comprehensible.
Note: In Unit Test, you would use expectations to handle asynchronous calls (see XCTest framework).
Refactor your code to use queue.sync and then throw your error from there. (Since your execute function is actually synchronous, given the group.wait() call at the last line, it shouldn't really matter.)
For instance, use this method from DispatchQueue:
func sync<T>(execute work: () throws -> T) rethrows -> T
By the way, a good idiom for leaving a DispatchGroup is:
defer { group.leave() }
as the first line of your sync/async block, which guarantees you won't accidentally deadlock when an error happens.

Combining two Observable<Void>s

I'm still a reactive newbie and I'm looking for help.
func doA() -> Observable<Void>
func doB() -> Observable<Void>
enum Result {
case Success
case BFailed
}
func doIt() -> Observable<Result> {
// start both doA and doB.
// If both complete then emit .Success and complete
// If doA completes, but doB errors emit .BFailed and complete
// If both error then error
}
The above is what I think I want... The initial functions doA() and doB() wrap network calls so they will both emit one signal and then Complete (or Error without emitting any Next events.) If doA() completes but doB() errors, I want doIt() to emit .BFailed and then complete.
It feels like I should be using zip or combineLatest but I'm not sure how to know which sequence failed if I do that. I'm also pretty sure that catchError is part of the solution, but I'm not sure exactly where to put it.
--
As I'm thinking about it, I'm okay with the calls happening sequentially. That might even be better...
IE:
Start doA()
if it completes start doB()
if it completes emit .Success
else emit .BFailed.
else forward the error.
Thanks for any help.
I believe .flatMapLatest() is what you're looking for, chaining your observable requests.
doFirst()
.flatMapLatest({ [weak self] (firstResult) -> Observable<Result> in
// Assuming this doesn't fail and returns result on main scheduler,
// otherwise `catchError` and `observeOn(MainScheduler.instance)` can be used to correct this
// ...
// do something with result #1
// ...
return self?.doSecond()
}).subscribeNext { [weak self] (secondResult) -> Void in
// ...
// do something with result #2
// ...
}.addDisposableTo(disposeBag)
And here is .flatMapLatest() doc in RxSwift.
Projects each element of an observable sequence into a new sequence of observable sequences and then
transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. It is a combination of map + switchLatest operator.
I apologize that I don't know the syntax for swift, so I'm writing the answer in c#. The code should be directly translatable.
var query =
doA
.Materialize()
.Zip(doB.Materialize(), (ma, mb) => new { ma, mb })
.Select(x =>
x.ma.Kind == NotificationKind.OnError
|| x.mb.Kind == NotificationKind.OnError
? Result.BFailed
: Result.Success);
Basically the .Materialize() operator turns the OnNext, OnError, and OnCompleted notifications for an observable of type T into OnNext notifications for an observable of type Notification<T>. You can then .Zip(...) these and check for your required conditions.
I've learned RxSwift well enough to answer this question now...
func doIt() -> Observable<Result> {
Observable.zip(
doA().map { Result.Success },
doB().map { Result.Success }
.catch { _ in Observable.just(Result.BFailed) }
) { $1 }
}