ReactiveCocoa - flattenMap block never called - ios

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.

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.

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.

NSProxy and forwardInvocation: invoke called within a block causes nil return value

I am using a NSProxy subclass and forwardInvocation: for capturing calls to my Backend API object (a shared instance).
Some Background information:
I want to capture the API calls so I can check everytime if I have to refresh my authentication token. If yes I just perform the refresh before.
The method parameters (of invocation) contain blocks.
Some simplified code:
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation setTarget:self.realAPI];
[invocation retainArguments];
// Perform refresh call and forward invocation after
// successfully refreshed
if (authenticationRefreshNeeded) {
[self.realAPI refreshWithBlock:^(NSObject *someObject) {
[invocation invokeWithTarget:self.realAPI];
}];
}
// Otherwise we just forward the invocation immediately
else {
[invocation invokeWithTarget:self.realAPI];
}
return;
}
I am already calling retainArguments so my blocks and other parameters don't get lost because of the late execution of invokeWithTarget: (refreshWithBlock: makes an async API call).
Everything works fine so far - BUT:
The return value of invocation is always nil when invokeWithTarget: is performed within the refresh block. Is there any way to retain the return value (like the arguments)?
Any hints? Suggestions?
Update
As response to #quellish:
The problem is that the return value is of type NSURLSessionDataTask (that I use to show an activity indicator) which I read directly after making the call. But the proxy does not forward the call immediately so the return value is not there - of course (I was blind).
What would be a possible workaround? Can I return a placeholder value or how can I know as caller when the method gets invoked so I can retrieve the return value later?
To perform an operation when your invocation is complete, passing the result:
if (authenticationRefreshNeeded) {
[self.realAPI refreshWithBlock:^(NSObject *someObject) {
NSURLSessionDataTask *resultTask = nil;
[invocation invokeWithTarget:self.realAPI];
[invocation getReturnValue:&resultTask];
if (completion != nil){
completion(resultTask);
}
}];
}
Where completion() is a block that takes an NSURLSessionDataTask as a parameter. Blocks can be used as callbacks, which make them well suited to what you are trying to do ("when I'm done, do this() ") Ideally, this would have been passed into the method containing the above - but since this is forwardInvocation: , that gets a little more... challenging. You could set it as a property on this proxy object and read it from there.
Another approach would be to extend UIApplication with a category or informal protocol with a method like addDataTask: which you could call instead of your block, which would hand off responsbility for the "i just added a data task" to another receiver, most likely the application's delegate (and you can extend the UIApplicationDelegate protocol with a new method, application:didAddDataTask: to handle this). It sounds like your data task and activity indicator are application-level concerns, which may make this a good fit.
That said, I have some experience with almost exactly the problems you are trying to solve (token based authorization). I would suggest taking at a look at how ACAccountStore approaches this problem , it may offer some ideas for alternative implementations.

View Controller state update on model update with ReactiveCocoa

As I'm slowly trying to wrap my head around ReactiveCocoa I wrote this piece of code and I'm fairly sure there's a better way to solve my problem. I'd appreciate input on how to improve / redesign my situation.
#weakify(self);
[RACObserve(self, project) subscribeNext:^(MyProject *project) {
#strongify(self);
self.tasks = nil;
[[[project tasks] takeUntilBlock:^BOOL(NSArray *tasks) {
if ([tasks count] > 0) {
MyTask *task = (MyTask *)tasks[0];
BOOL valid = ![task.projectID isEqualToString:self.project.objectID];
return valid;
}
return NO;
}] subscribeNext:^(NSArray *tasks) {
self.tasks = tasks;
}];
}];
What this does:
I have a View Controller with a property called project of type MyProject and a property tasks of type NSArray. A project has a tasks signal that returns an array of MyTasks. The project can be changed at any time from the outside. I want my view controller to respond and refresh itself when said case occurs.
Problem I'm trying to solve:
I used to [[project tasks] subscribeNext:...] within the first block, until I realized that if the webrequest took too long and I switched the project in the meantime, I received and assigned data from the old project in the new context! (Shortly thereafter the new data set arrived and everything went back to normal).
Nevertheless, that's the problem I had and I solved it by using the takeUntilBlock: method. My question is: How can I simplify / redesign this?
The crucial operator to most naturally take the tasks of the most recent project is -switchToLatest. This operator takes a signal of signals and returns a signal that sends only the values sent from the latest signal.
If that sounds too abstract, it will help to put it in terms of your domain. First, you have a signal of projects, specifically RACObserve(self, project). Then, you can -map: this project signal into a signal that contains the result of the call to -tasks, which happens to return a signal. Now you have a signal of signals. Applying -switchToLatest to the signal of task signals will give you a signal of tasks, but only sending tasks from the most recent project, never "old" tasks from a previously assigned project.
In code, this looks like:
[[RACObserve(self, project)
map:^(MyProject *project) {
return [project tasks];
}]
switchToLatest];
Another idiom you can apply to simplify your code is to use the RAC() macro, which assigns to a property while avoiding explicit subscription.
RAC(self, tasks) = [[RACObserve(self, project)
map:^(MyProject *project) {
return [project tasks];
}]
switchToLatest];
Update
To address the questions in the comments, here's an example of how you could initialize the tasks property to nil following an change of the project, and also a simplistic approach to handling errors in the -tasks signal.
RAC(self, tasks) = [[RACObserve(self, project)
map:^(MyProject *project) {
return [[[project
tasks]
startsWith:nil]
catch:^(NSError *error) {
[self handleError:error];
return [RACSignal return:nil];
}];
}]
switchToLatest];

ios: waiting for method to finish executing before continuing

I am new to IOS development and am currently facing a problem.
When method A is called, it calls method B and then it wait for delegate connectionDidFinish which connectionDidFinish will execute MethodC.
My question is how do I ensure that methodA to methodC has finished executing before executing NSLog?
I found that a way to solve this problem is to use notification center. Send notification to me after finishing executing methodC. I don't think this is a good solution. Is there another way to do this?
Example:
[a methodA];
NSLog(#"FINISH");
If any of those methods perform actions asynchronously, you can't. You'll have to look into a different way of doing this. I personally try to use completion blocks when ever I can, although it's perfectly fine to do this other ways, like with delegate methods. Here's a basic example using a completion block.
- (void)someMethod
{
[self methodAWithCompletion:^(BOOL success) {
// check if thing worked.
}];
}
- (void)methodAWithCompletion:(void (^) (BOOL success))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, kNilOptions), ^{
// go do something asynchronous...
dispatch_async(dispatch_get_main_queue(), ^{
completion(ifThingWorked)
});
});
}
In the code you posted, methodA must finish executing before the log statement will execute.
However, if methodA starts an asynchronous process that takes a while to finish and returns before it is finished, then you need to do something different. Usually you don't want to freeze the user interface while you are waiting, so you set up a delegate, pass in a completion block, or wait for an "ok, I'm done" notification.
All those are very valid, good ways to solve the problem of waiting for asynchronous tasks to finish running.
Newer APIs are starting to use completion blocks. Examples are:
presentViewController:animated:completion:, which takes a completion
block that gets called once the new view controller is fully
on-screen and "ready for business.
animateWithDuration:animations:completion:, which takes a completion
block that gets executed once the animation is finished, and
sendAsynchronousRequest:queue:completionHandler:, which starts an
asynchronous URL request (usually an HTTP GET or PUT request) and
provides a completion block that gets called once the request has
been completed (or fails)

Resources