RXSwift - takeUntil canceling before next event - ios

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?

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.

Related

RxSwift merge two api request into one result clears first result

I have a refreshTrigger and BehaviourRelay of items:
var refreshTrigger = PublishSubject<Void>()
var rootItems: BehaviorRelay<[Codable]> = BehaviorRelay(value: [])
Then, I use UITextField to run search query when user enters text:
let queryText = queryTextField.rx.text.orEmpty
.throttle(.milliseconds(300), scheduler: MainScheduler.instance)
.distinctUntilChanged()
Finally, I have an observable which combines this observable with additional trigger to refresh manually:
let refreshText = refreshTrigger.withLatestFrom(queryText)
Observable.merge(refreshText, queryText)
.flatMapLatest { [weak self] query -> Observable<[Codable]> in
guard let strongSelf = self else { return .empty() }
let ctgs = try strongSelf.getCategories()
.startWith([])
.catchErrorJustReturn([])
let itms = try strongSelf.getSearchResults(query)
.retry(3)
.startWith([])
.catchErrorJustReturn([])
return Observable.merge(ctgs, itms)
}
.bind(to: rootItems)
.disposed(by: disposeBag)
As you can see, I want to send 2 requests: fetch categories and items, because I'm displaying them in the same UITableView. It sends both requests at the same time, but first result disappear when the second comes in. But I use merge, so it should work.
Why it doesn't show combined results?
Headers of the getCategories and getSearchResults looks like this:
func getSearchResults(_ text: String) throws -> Observable<[Codable]>
func getCategories() throws -> Observable<[Codable]>
they both use alamofire's rx extension to run queries.
Both of your getters return Observable arrays. This means that when the call completes, the observable emits an array of items. When you merge the two Observables, the follow on code can't distinguish between the items from one getter and the items from the other getter. It just sees an array come in (from one of them,) then another array come in (from the other.) In other words, you misunderstood how merge works.
To achieve the result you want, you should use zip or possibly combineLatest instead of merge. Something like this:
Observable.zip(ctgs, itms) { $0 + $1 }

Call two Swift Combine calls where the second call depends on the result of the first call

I'm new to Swift Combine, so having a small issue
Im trying to call two api calls, and merge the results as the second API call depends on the result from the first call.
Here is the code
return self.gameRepository.fetchGames(forUser: user.id)
.map { games -> AnyPublisher<[Game], ApiError> in
let result = games.map { gameDto -> Game in
let location = self.venueRepository.fetch(by: gameDto.siteId)
.map { $0.mapToModel() }
let game = gameDto.mapToModel(location: location)
return game
}
My error is on line 4 "let location" the compiler complains when I try and pass this through to
line 5
game.mapToModel(location: location)
Cannot convert value of type 'AnyPublisher' to expected argument type Location
The fetch call signature from the repository looks like this
func fetch(by id: String) -> AnyPublisher<LocationDto, ApiError>
So it is correct, but the .map call I use on the result allows the
$0.mapToModel()
to occur, so I have locationDto object that allows me to cast to my Domain model.
Any help on how I can call these two apis together would be much appreciated.
Instead of the first map operator, try to use flatMap and return from this .flatmap AnyPublisher with your model, that needs to be passed to another request. Pseudocode:
fetchGames -> AnyPublisher<[Game]> // First request
fetchGame(by id: String) -> AnyPublisher<LocationDto> // Second request that depends on model from first one
fetchGames.flatMap { fetchGame(by: $0.id) }

Is it possible to use Observable<Double>.interval in RxSwift?

Hi I have a question here, all the docs I found on RxSwift so far are using observable interval as below:
let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
.subscribe { event in
print(event)
}
NSThread.sleepForTimeInterval(2)
subscription.dispose()
Now I have the need to implement a Observable<Double>.interval timer, I would like to a Double value in my subscribeNext calls from Observable.
After change above code to Double as a testing, I have an error saying Type Observable<Double> has no member IntegerLiteralType , anyone knows how to implement this in RxSwift?
extension Observable where Element : SignedIntegerType {
/**
Returns an observable sequence that produces a value after each period, using the specified scheduler to run timers and to send out observer messages.
- seealso: [interval operator on reactivex.io](http://reactivex.io/documentation/operators/interval.html)
- parameter period: Period for producing the values in the resulting sequence.
- parameter scheduler: Scheduler to run the timer on.
- returns: An observable sequence that produces a value after each period.
*/
#warn_unused_result(message="http://git.io/rxs.uo")
public static func interval(period: RxTimeInterval, scheduler: SchedulerType)
-> Observable<E> {
return Timer(dueTime: period,
period: period,
scheduler: scheduler
)
}
}
The Element Type is SignedIntegerType. So you can't use Double.If you want to use double , you can rewrite Timer and associated classes.But I don't suggest to rewrite it.You can think another way to do it.
I am not quite sure why you would want a sequence of Double values, because the value just tells you how many times the interval has passed (it does not tell you the amount of time that has passed). So it makes sense that the value is an Int.
But if you really want a sequence of Double values you could easily map the Int sequence to a Double sequence:
_ = Observable<Int>.interval(0.4, scheduler: MainScheduler.instance)
.map { Double($0) }
.subscribeNext { value in
print(value)
}
.addDisposableTo(disposeBag)
Which prints:
1.0
2.0
3.0
4.0
...

How to convert Observable array of a type to Observable array of a different type with RxSwift

I'm new to RxSwift and I came across the next situation:
self.viewModel!.country
.flatMapLatest { country -> Observable<City> in
country!.cities!.toObservable()
}
.map { city -> SectionModel<String, String> in
SectionModel(model: city.name!, items: city.locations!)
}
.toArray()
.bindTo(self.tableView.rx_itemsWithDataSource(dataSource))
.addDisposableTo(self.disposeBag)
Seems to me like toArray() does nothing and I don't know why. On the other hand this code does what I want. I would like to know why the previous code does not work the same way:
self.viewModel!.country
.flatMapLatest { country -> Observable<[SectionModel<String, String>]> in
var models = [SectionModel<String, String>]()
for city in country!.cities! {
models.append(SectionModel(model: city.name!, items: city.locations!))
}
return Observable.just(models)
}
.bindTo(self.tableView.rx_itemsWithDataSource(dataSource))
.addDisposableTo(self.disposeBag)
Thanks in advance.
EDIT:
View model implementation is as follow:
class LocationViewModel {
let country = BehaviorSubject<Country?>(value: nil)
}
Country has a property 'cities' which is an array of City.
#solidcell You must be right, if I put debug() before and after toArray() I get 2 subscriptions, one for each debug(), and 1 next event for each array item only for the before toArray() debug().
But then, why isn't it completing?
It's probably because country!.cities for some countries doesn't terminate. toArray() will not emit any elements until the Observable completes. Only once the Observable completes does it package all of the elements into one single element as an Array. However, I can't say for certain if this is your issue, since I don't know how you've implemented country!.cities.
Try putting a .debug() in there and see if you can spot if it's because an Observable of cities isn't completing.

Closures In Swift?

I am new to iOS coding and I am stuck in closures feature of SWIFT. I have referred to many tutorials and found that closures are self written codes which can be used in many ways eg. as arguments in function call,parameters in function definition,variables. I am giving below an example below with my associated thoughts about the code & questions. Please help me if I am wrong in my understanding. I know I am wrong at many points,so please rectify me.
1.1st Part
func TEST(text1:String,text2:String,flag: (S1:String,S2:String)->Bool)//In this line,I think,I am using flag is a closure which is passed as parameter in a function. And if so why doesn't it follow the standard closure syntax?
{
if flag(S1: text1, S2: text2) == true//I want to check the return type what flag closure gets when it compares the both string during function call. Why can't I write as if flag == true as flag is the name of the closure and ultimately refers to the return type of the closure?
{
print("they are equal")
}
else
{
//
}
}
2nd Part
This part is the most troublesome part that really confuses me when I am calling the function. Here I am also using the same closure. What is happening over here? How is the closure being used? Is it capturing values or something else?
TEST("heyy", text2: "heyy") { (S1, S2) -> Bool in
S1==S2
}
Thanks for your kind consideration.
Your closure usage is ok. A closure is some code that can be passed to be executed somewhere else. In your case you can choose to pass the real test you want to the function TEST, simple string test or case-insensitive test, etc. This is one of the first usage of closure: obtain more genericity.
And yes closures capture something, it captures some part of the environnement, i.e. the context in which they are defined. Look:
var m = "foo"
func test(text1:String, text2:String, testtFunc: (s1:String, s2:String) -> Bool) {
m = "bar"
if testFunc(s1: text1, s2: text2) { print("the test is true") }
}
m = "baz"
test("heyy", text2: "heyy") { (s1, s2) -> Bool in
Swift.print("Value for m is \(m)")
return s1==s2
}
The closure captures m (a variable that is defined in the context in which you define the closure), this means that this will print bar because at the time the closure is executed, the captured m equals to bar. Comment bar-line and baz will be printed; comment baz-line and foo will be printed. The closure captures m, not its value, m by itself, and this is evaluated to the correct value when the closure is evaluated.
Your first function works like this :
arguments :
String 1
String 2
a function that takes two Strings as arguments and returns a Bool
body :
execute the function (flag) with text1 and text2 and check the result.
The function doesn't know at all what you are testing, it only knows that two pieces of text are needed and a Bool will be returned.
So this function allows you to create a general way of handling different functions that all have two Strings as input. You can check for equality or if the first pieces of text is a part of the second and so on.
This is useful for many things and not so far from how array filtering / sorting / map works.
2nd Part :
This is just how you call a function with a closure.
TEST("heyy", text2: "heyy") { (S1, S2) -> Bool in
S1 == S2
}
You can also call it like this :
func testStringEqualityFor(text:String, and:String) -> Bool {
return text == and
}
TEST("hey", text2: "hey", flag: testStringEqualityFor)
Instead of using the trailing closure syntax to pass an unnamed function, you now pass a named function as one of the arguments.
It al becomes a lot clearer when you simplify it.
This is a function that takes another function as an argument.
Now we can call/use this function inside it. The argument function takes a bool as it's argument. So we give it a true
func simpleFunctionWithClosure(closure:(success:Bool) -> Void) {
// use the closure
closure(success: true)
}
When we use the function we need to pass it a function. In Swift you have the trailing closure syntax for that, but that is only available (and even then optional) to the first function as argument.
Trailing closure syntax means that instead of passing a named function you can write:
myFunction { arguments-for-closure-as-tuple -> return-for-closure-as-tuple in
function-body
}
The closure will receive an argument of Bool and returns nothing so Void.
In the body we can handle the arguments and do stuff with them.
But it is important to remember that what is inside the closure is not called directly. It is a function declaration that will be executed by simpleFunctionWithClosure
// use the function
simpleFunctionWithClosure { (success) -> Void in
if success {
print("Yeah")
} else {
print("Ow")
}
}
Or with a named function :
func argumentFunction(success:Bool) -> Void {
if success {
print("Yeah")
} else {
print("Ow")
}
}
simpleFunctionWithClosure(argumentFunction)
Compiler wouldn't have any expectation how your closure is for. For example in your first case, it could not estimate the closure's intake parameters is always just reflect to the first and second parameters of the TEST function when we are always able to write the following code :
func Test(str1:String,str2:String,closure:(String,String)->Bool){
if closure(str[str1.startIndex...str1.startIndex.advanced(2)],str2[str2.startIndex.advanced(1)...str2.endIndex])
{ ... }else{ ... }
//Just an example, everybody know no one write their code like this.
}
The second case, I thought you've just overlooked the syntax sugar:
For a trailing closure A->B:
{ a:A -> B in a.bValue() }
is equal to :
{ a:A -> B in return a.bValue() }
Also, I think this TEST function isn't a good example when the task of it can be done without using closure. I think you can write a map function by yourself for a better understand of why and when to use closure.

Resources