Unable to filter RACSignal events - ios

My ViewController contains a UISearchBar and implements the UISearchBarDelegate protocol. I've created a signal for searchBartextDidChange: that fires correctly with subscribeNext:
RACSignal *searchTextChangeSignal = [self rac_signalForSelector:#selector(searchBar:textDidChange:) fromProtocol:#protocol(UISearchBarDelegate)];
[searchTextChangeSignal subscribeNext:^(id x){
// This works.
}];
At this point, I'd like to filter the results of this filter to 1) Only include text that is greater than 3 characters, and 2) throttled by 300 ms. My attempt:
[[searchTextChangeSignal filter:^(RACTuple *tuple) {
NSString *textEnteredIntoSearchBar = (NSString *)tuple.second;
return textEnteredIntoSearchBar.length > 3;
}] throttle:300];```
The code above doesn't work. The blocks are never executed. If I replace the filter method with subscribeNext, the subscribeNext block does execute. Furthermore, XCode autocompletes the filter method above, so the method is available. Is there something I'm missing here? What's the correct way to do this? Any help is much appreciated.

The missing understanding is that signals do not perform any work until they are subscribed to. Call one of the subscribe methods following the throttle, and you will see the data start to flow through.

Related

ReactiveCocoa - flattenMap block never called

I have a problem using flattenMap method of RACSignal - the block never gets called. If I subscribeNext to the same signal, it works just fine, the problem is only with flattenMap.
Here's what works fine
[[self.aSignal combineLatestWith:self.otherSignal] subscribeNext:^(RACTuple *tuple) {
// gets called just fine
}];
And here's what doesn't work:
self.yetAnotherSignal = [[self.aSignal combineLatestWith:self.otherSignal] flattenMap:^RACStream *(RACTuple *tuple) {
// never gets called
return returnSignal;
}];
Am I missing something? Or do I misunderstand how flattenMap works?
It seems like you're missing just one little bit: (at least in your snippet) no one is subscribing to your new signal!
You're constructing a new signal (self.yetAnotherSignal) from self.aSignal and self.otherSignal via combineLatest and flattenMap.
But that new signal, as well as any operators in the chain, do not actually do any work until it is subscribed in some form, the simplest form being via subscribeNext just as you did in your first snippet.
That is not just the case with flattenMap, its the same with any operation, e.g. combineLatestWith in your first example would not do anything if you would not subscribe to it. The same goes for map, filter, ... you name it.

Will code wait for return before continuing?

I using the below code to set elements in an array and return the array, the returned array is then passed to another method for more changes before again being returned.
NSMutableArray *returnArray = [[NSMutableArray alloc]init];
//call checkTP1
returnArray = [self checkTP1STD:addingTime :startToTP1 :TP1Result :nowDate :sevenHour :totalrest :returnArray];
//call check TP2
returnArray = [self checkTP2STD:addingTime :startToTP2 :TP2Result :nowDate :sevenHour :totalrest :returnArray :tp2Rest];
It is currently working as expected, my question is will it always wait for the checkTP1STD to return before executing checkTP2STD?
I have split the code into multiple methods to enable it to be more readable as i will be adding some other logic to pass different variable values to the methods,just wanted to make sure my basic idea will work.
In general: yes
Your question is curious, you seem to be concerned that checkTP1STD will return before checkTP2STD is called, but not that the calls to alloc and init will return before the call to checkTP1STD.
Are you actually intending to do asynchronous work in checkTP1STD (E.g. Using GCD or system framework methods which state they are async). If so the answer is still yes, but the call may return before all the work scheduled by checkTP1STD is complete - the very nature of asynchronous programming.
HTH
In short, yes. Code is executed in sequential order unless there's explicit calls to new threads, which you're not doing by the code you've given.

RACSubject and disposal

I am trying to create a signal that will cancel a NSURLSessionDataTask upon disposal. The problem is that I am not able to wait for the task to finish until I can send next values (implementing Server-sent Events), but I have to use the NSURLSessions delegate methods.
What I am doing right now is creating a RACSubject and returning it for every new request. Upon new events arrive, I sendNext: on the subject. The problem I have is figuring out when to efficiently cancel the task, if there are no more subscribers on the subject.
A workaround I found so far is creating a dummy signal and merging it with the subject (see below).
return [[RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
return [RACDisposable disposableWithBlock:^{
if ( dataTask.state != NSURLSessionTaskStateCanceling && dataTask.state != NSURLSessionTaskStateRunning ) {
[dataTask cancel];
}
}];
}]
merge:self.requests[#(dataTask.taskIdentifier)][kSubjectKey]];
But there has to be a more elegant way, or? Plus a downside is, that the signal will never complete. If I sendCompleted within the dummy signal, the dispose block will be called immediately.
I am using ReactiveCocoa 2.5.x
Have you checked out this library. Basically all you need is to turn the delegate methods into blocks and then you can use declare the blocks inside of creatSignal:call. Check out this post if you want to wrap the delegate methods into blocks yourself.

ReactiveCocoa, combine two signals on button enabled

I'm using MVVM architecture and I have two signals:
RACSignal *internetEnabledSignal = RACObserve(self.regWizardVehicleViewModel, internetConnectionEnabled);
RACSignal *executingRegistrationSignal = RACObserve(self.regWizardVehicleViewModel, isExecuting);
I need to combine and bind signals on button enabled property. Button needs to be disabled if there is not any connection OR when some method is executing. I was searching and searching for solution but I don't know how to combine signals with OR. Is there any way to do that? Next question is: I'm using MBProgressHUD. I want to show that HUD while executing my async method in my Model. MBProgressHUD has show and hide methods, can't bind it to a property like button enabled?
There is a convenience method, -and which makes working with "boolean" value signals easier:
Returns a signal that applies AND to each NSNumber in the tuple.
To get a tuple with NSNumbers, use combineLatestWith: like this:
RAC(self.button, enabled) =[[[executingRegistrationSignal not]
combineLatestWith:internetEnabledSignal]
and];
If I'm not misunderstanding you, then the functionality should be easily achievable with combineLatest:reduce:, like so:
RACSignal * enabledSignal =
[RACSignal combineLatest:#[internetEnabledSignal, executingRegistrationSignal]
reduce:^id(NSNumber * internetEnabled, NSNumber * isExecuting) {
return #(internetEnabled.boolValue && !isExecuting.boolValue);
}].distinctUntilChanged;
RAC(self.button, enabled) = enabledSignal;
combineLatest:reduce: won't send any next events until all signals have fired at least once, so please keep that in mind.
Edit: Please see MichaƂ's answer for a much cooler solution via convenience methods.
As for your other question, I'm not very familiar with MBProgressHUD but this should do the trick:
[self.hud rac_liftSelector:#selector(show:) withSignalsFromArray:#[
[enabledSignal ignore:#NO]
]];
[self.hud rac_liftSelector:#selector(hide:) withSignalsFromArray:#[
[[enabledSignal ignore:#YES] mapReplace:#YES]
]];
This is a bit of a cheat, we're leveraging RAC's eager subscription to these signals to fire side effects, but if you're a pragmatist and not a pedantic idealist like me, you probably don't mind.
What's happening with this code is that we're asking RAC to perform the given selector after every one of the signals in the array we supplied fires, so it's a lot like combineLatest:. We're using mapReplace: because it'll use that signal as the (BOOL)animated argument (this is another reason why it's a cheat).

How to know when all objects are saved asynchronously using ReactiveCocoa

In my app, I am using ReactiveCocoa to return signals to notify me when async api calls are completed (successfully or not). On the POST for saving the data, it only takes one object at a time:
- (RACSignal *)postJSONData:(NSDictionary *)dict toRelativeURL:(NSString *)urlString;.
The function that returns a RACSignal sends the subscriber a next:
[subscriber sendNext:json]or an Error: [subscriber sendError:jsonError].
This works great when saving a single object but I also have a scenario where I have to save multiple objects. These objects can be saved in any order (i.e. they are not dependent on one another) or sequentially - it doesn't matter to me.
I need to update the UI indicating the overall progress (Saving 1 of 4, Saving 2 of 4....) as well as a final progress update (Completed 4 of 4) and specific action to take when all have been processed (successful or not).
There are a number of ways to do this, but I'd like to do this the proper way using ReactiveCocoa. I'm thinking I either can do this with a concat: or then: with a rac_sequence map:^, but I'm not sure. On their github page, they show an example of addressing parallel work streams, but they use 2 discretely defined signals. I won't have my signals until I loop through each object I need to save. Would love some guidance or an example (even better!). Thanks in advance.
I'm doing something similar in my app where I start 3 different async network calls and combine them all into one signal that I can listen to. Basically I loop through all my objects and store the network signal in an array. I then call merge: and pass it the array of network signals.
NSMutableArray *recievedNames = [NSMutableArray new];
NSMutableArray *signals = [NSMutableArray new];
//go though each database that has been added and grab a signal for the network request
for (GLBarcodeDatabase *database in self.databases) {
[signals addObject:[[[[self.manager rac_GET:[database getURLForDatabaseWithBarcode:barcode] parameters:nil] map:^id(RACTuple *value) {
return [((NSDictionary *)value.second) valueForKeyPath:database.path];
}] doError:^(NSError *error) {
NSLog(#"Error while fetching name from database %#", error);
}]
}
//forward all network signals into one signal
return [[[RACSignal merge:signals] doNext:^(NSString *x) {
[recievedNames addObject:x];
}] then:^RACSignal *{
return [RACSignal return:[self optimalNameForBarcodeProductWithNameCollection:recievedNames]];
}];
Feel free to ask me questions about any of the operators I have used and I will do my best to explain them.
I am just learning ReactiveCocoa myself but I wanted to point out something important which also agrees with lightice11's answer. concat combines signals sequentially. Meaning you won't get anything from #2 or #3 until #1 completes. merge on the other hand interleaves the responses returning whatever comes next regardless of order. So for your scenario, it sounds like you really do want merge.
To quote the man, Justin Spahr-Summers:
concat joins the streams sequentially, merge joins the signals on an as-soon-as-values-arrive basis, switch only passes through the events from the latest signal.

Resources