ReactiveCocoa: why subscriber has "sendNext" method rather "receiveNext" method - ios

I'm learning ReactiveCocoa and understand that RACSignal must be subscribed to a RACSubscriber for the signal to send the event. It is clear that RACSignal send event to RACSubscriber and RACSubscriber receive event from RACSignal. However, when you customize your own RACSignal by the following code:
RACSignal *racsignal = [RACSignal createSignal:^RACDisposable* (id<RACSubscriber> subscriber) {
//why subsriber "sendNext" not "receiveNext"?
[subscriber sendNext:#100];
return nil;
}];
The RACSubscriber protocol has sendNext method which confuses me because the method name of receiveNext should be more appropriate from my understanding. Can any body help me to clarify that?

Technically, the object implementing the RACSubscriber protocol doesn't consume the events, but forwards them to all subscribers. In your case there is only one, but the great thing about the RACSignal is that it can be observed from different objects and threads.
So you are right that the naming of RACSubscriber might be a bit misleading, but I wouldn't put to much attention on that, just as the documentation say:
You generally shouldn't need to implement this protocol. +[RACSignal
createSignal:], RACSignal's subscription methods, or RACSubject should
work for most uses.

Related

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.

Unable to filter RACSignal events

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.

ReactiveCocoa conditional async signals

I have a merge operation that depends on the result of two asynchronous operations. The first is a network operation, the second is a success or failure of location authorization. I don't care about the values of these operations, just that both have completed.
This is what it looks like:
RACSignal *networkCallReturned = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:kNetworkCallReturned object:nil] take:1];
RACSignal *locationPermission = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:kLocationManagerGotLocationPermission object:nil] take:1];
#weakify(self);
[[RACSignal merge:#[ networkCallReturned, locationPermission ]
subscribeCompleted:^{
#strongify(self);
// Do something else here
}];
The problem I am having is that the network call is not made when I do not have reachability. This is not something I can change either. How can I conditionally fire the networkCallReturned signal if I do not have reachability?
Do I have to setup another signal that monitors reachability and then take the first value sent from either networkCallReturned or the reachability signal?
You could monitor reachability, but it's infamously fraught with races and edge cases. It seems like you'd be much better served by catching the errors that from from not being able to complete the network call, or timing out the network call.

Resources