Rodrigo Ruiz Rodrigo Ruiz - 5 months ago 13
iOS Question

RXSwift - takeUntil canceling before next event

Following a similar example to question 39 here: http://reactivex.io/learnrx/

I'm trying to transform a method call

search(query: String)
into a sequence of those calls.
They way I'm achieving this is by creating a
Variable
which I update with the
query
value every time
the
search(query: String)
method is called.

Then I have this in my
init()
:

_ = queryVariable.asObservable().flatMap({ query -> Observable<[JSON]> in
return self.facebookSearch(query).takeUntil(self.queryVariable.asObservable())
}).subscribeNext({ result in
if let name = result[0]["name"].string {
print(name)
} else {
print("problem")
}
})


If I type
"ABC"
, my
search(query: String)
method will be called 3 times with
"A"
,
"AB"
,
"ABC"
.
That would be mapped to
seq(["A", "AB", "ABC"])
with
queryVariable.asObservable()
.
Then I'm mapping it to Facebook searches (searching people by their names on Facebook).
And with
subscribeNext
I print the name.
If I don't use the
takeUntil
, it works as I'd expect, I get 3 sets of results, one for each of my queries(
"A"
,
"AB"
,
"ABC"
).

But if I type fast (before Facebook has time to respond to the request), I'd want only one result, for the query
"ABC"
. That's why I added the
takeUntil
. With it I'd expect the
facebookSearch(query: String)
call to be ignored when the next
query
comes in, but it is being canceled for the current query, so with this
takeUntil
I end up printing nothing.

Is this a known issue or am I doing something wrong?

Answer

I used your code and found two solutions to your problem:

1. Use flatMapLatest

You can just use flatMapLatest instead of flatMap and takeUntil. flatMapLatest only returns the results of the latest search request and cancels all older requests that have not returned yet:

_ = queryVariable.asObservable()
    .flatMapLatest { query -> Observable<String> in
        return self.facebookSearch(query)
    }
    .subscribeNext {
        print($0)
    }

2. Use share

To make your approach work you have to share the events of your queryVariable Observable when you also use it for takeUntil:

let queryObservable = queryVariable.asObservable().share()

_ = queryObservable
    .flatMap { query -> Observable<String> in
        return self.facebookSearch(query).takeUntil(queryObservable)
    }
    .subscribeNext {
        print($0)
    }

If you do not share the events, the searchQuery.asObservable() in takeUntil creates its own (duplicate) sequence. Then when a new value is set on the searchQuery Variable it immediately fires a Next event in the takeUntil() sequence and that cancels the facebookSearch results.

When you use share() the sequence in takeUntil is observing the same event as the other sequence and in that case the takeUntil sequence handles the Next event after the facebookSearch has returned a response.

IMHO the first way (flatMapLatest) is the preferred way on how to handle this scenario.