RACCommand separate handling of successful completion - ios

I have a problem with implementing some logical simply code.
In simple terms I want to make something like this:
Complete command() {
if (error) {
show error alert
}
else {
show "Happy ending" allert
}
}
I know how to make first part, but with second I have a problem
My try:
[self.vm.applyCommand.errors subscribeNext:^(id x) {
//Show alert with error
}];
[[[self.vm.applyCommand.executionSignals
map:^(RACSignal *signal) {
return [[signal ignoreValues] materialize];
}]
concat]
subscribeNext:^(RACEvent *event) {
//Show alert with "Happy end"
}];
In this case everything works as expected until error appears. In case of error I see two alerts (one with error, another with "Happy end"), but want only one with error.
I know why it happens,
Errors will be automatically caught upon the inner signals ...etc
but I want some solution to make desirable behavior.
UPD: "...see two alerts (one with error, another with "Happy end")"

There are two possible ways to achieve what you want, depending on how the execution signal for applyCommand behaves.
If it sends only one next value and then immediately completes, you can simply use switchToLatest operator to subscribe for that next value and display the alert with 'Happy end':
[[self.command.executionSignals switchToLatest] subscribeNext:^(id x) {
//Show alert with "Happy end"
}];
Otherwise it's much more complicated, because it's hard to distinguish between successful completion and failure of RACCommand's execution signal. In case of error you get a "completed" RACEvent when calling [[signal ignoreValues] materialize];, and then command's errors signal sends the error as its next value.
I managed to do it using command's executing signal, which sends #NO after the errors signal sends the error. You can use merge and combinePreviousWithStart:reduce operators to check if the command stopped executing because an error occurred:
RACSignal *stoppedExecuting = [[self.vm.applyCommand.executing ignore:#YES] skip:1];
RACSignal *merged = [stoppedExecuting merge:self.vm.applyCommand.errors];
[[[merged combinePreviousWithStart:nil reduce:^id(id previous, id current) {
return #( [previous isKindOfClass:[NSError class]] || [current isKindOfClass:[NSError class]] );
}] filter:^BOOL(NSNumber *errorOccurred) {
return !errorOccurred.boolValue;
}] subscribeNext:^(id x) {
NSLog(#"Happy end!");
}];
It's not a perfect solution as it depends on order of sending values from various RACCommand's signals, which is an implementation detail and can change in the future (it worked for me with RAC 2.5).
I suppose that this problem might be solved with RAC 3.0, as it replaces RACCommand with Action, but I haven't tried it yet.

Related

ReactiveCocoa :: Can I subscribe to a signal inside a createsignal?

We are having some problems trying to get CLLocationManagerworking with iOS8.
The idea is that we have a LocationManager that handles all location related stuff. Since iOS8 the ask for permissions deal is asynchronous, so when we try to get a location we might not have received permissions yet.
To work this around we want to do it in a two-step fashion:
First check if we already have authorization. If so, tell the subscriber completed.
Otherwise, start updating, which will ask for permission. Once the user replied, get the status from the callback. If YES, send completed. If NO, send error.
In the code below, self.authorizationStatusSignal is observing the callback didChangeAuthorizationStatus, so it will trigger whenever the user decides to give permission (or not).
The thing is that, upon subscribing to that signal inside the creation method, the subscriber has lost reference and the completed is never delivered.
Is it possible to subscribe like this inside the creation? We tried strongifying it but nothing happened.
- (RACSignal *)authorizationSignal {
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
if ([self authorized:#([CLLocationManager authorizationStatus])]) {
[subscriber sendCompleted];
} else {
[self.authorizationStatusSignal subscribeNext:^(RACTuple * args) {
if ([self authorized:(NSNumber *)args.second]) {
[subscriber sendCompleted];
} else {
[subscriber sendError:nil];
}
}];
}
return nil;
}] replayLast];
}
After the idea proposed by #kenKuan we did another check that hadn't think about. The problem lies in the sendError that is executing (though it shouldn't reach that instance) before we can actually send completed. This way, it prevents the sendcompleted from actually reaching the subscriber.

Handling next, completed and error in ReactiveCocoa

I am still fairly new in the ReactiveCocoa world and I just wanted to get this common scenario clarified. I noticed that other people are struggling with this matter on GitHub and SO, but I am still missing a proper answer.
The following example does work, but I saw that Justin Summers says that subscriptions-within-subscriptions or subscriptions in general could be code smell. Therefor I want to try and avoid bad habits when learning this new paradigm.
So, the example (using MVVM) is pretty simple:
A ViewController contains a login button which is connected to a login command in the viewmodel
The ViewModel specifies the command action and simulates some network request for this example.
The ViewController subscribes to the command's executingSignals and is able to differentiate the three types of returns: next, error and complete.
And the code.
1 (ViewController):
RAC(self.loginButton, rac_command) = RACObserve(self, viewModel.loginCommand);
2 (ViewModel):
self.loginCommand = [[RACCommand alloc] initWithEnabled:canLoginSignal
signalBlock:^RACSignal *(id input) {
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
BOOL success = [username isEqualToString:#"user"] && [password isEqualToString:#"password"];
// Doesn't really make any sense to use sendNext here, but lets include it to test whether we can handle it in our viewmodel or viewcontroller
[subscriber sendNext:#"test"];
if (success)
{
[subscriber sendCompleted];
} else {
[subscriber sendError:nil];
}
// Cannot cancel request
return nil;
}] materialize];
}];
3 (ViewController):
[self.viewModel.loginCommand.executionSignals subscribeNext:^(RACSignal *execution) {
[[execution dematerialize] subscribeNext:^(id value) {
NSLog(#"Value: %#", value);
} error:^(NSError *error) {
NSLog(#"Error: %#", error);
} completed:^{
NSLog(#"Completed");
}];
}];
How would you do this in a more ReactiveCococa-kind-a-way?
With the way RACCommand works, values come from the executionSignals signal, errors from the errors signal, and completions, well, those are where one might use -materialize and -dematerialize as in your example.
In the example given, login, it arguably don't require completion to model it. Instead a login signal could be defined to be binary in behavior: it either sends #YES (for example), or sends an error. Under these conditions, the code would be:
[[self.viewModel.loginCommand.executionSignals concat] subscribeNext:^(id _) {
// Handle successful login
}];
[self.viewModel.loginCommand.errors subscribeNext:^(NSError *error) {
// Handle failed login
}];
This is obviously a bit of a divergence from the typical subscribeNext:error:completed: pattern typical in RAC. That's just due to RACCommand's API.
Note that the -concat operator has been applied to executionSignals in order to surface the inner values and avoid inner subscriptions. You might also see -flatten or -switchToLatest used in other RACCommand examples, but whenever a command has its allowsConcurrentExecution property set to NO (which is the default), then execution happens serially, making -concat the operator that naturally matches and expresses those serial semantics. Applying -flatten or -switchToLatest would actually work, since they degenerate to -concat when applied to serial signal-of-signals, but they express semantics to the reader that don't apply.

Why cancelled AFHTTPRequestOperation sometimes hit the success block?

I'm integrating autocomplete on a search bar through Google Places API. And for the networking requests, I use AFNetworking.
I want to have only one request running at a time. So everytime I type a new letter, I do the following:
1 - Cancel the previous AFHTTPRequestOperation:
[self.currentGlobalSearchListRequest cancel];
NSLog(#"%# cancelled", self.currentGlobalSearchListRequest);
2 - Start a new AFHTTPRequestOperation:
self.currentGlobalSearchListRequest = [searchService getGlobalSearchListItemsForString:searchString inRegion:region delegate:self];
NSLog(#"%# started", self.currentGlobalSearchListRequest);
Here is the callback called when the request has finished running:
- (void)request:(PointSearchRequest *)request didLoadSearchListItems:(NSArray *)searchListItems {
NSAssert(request == self.currentGlobalSearchListRequest, #"!!!callback from a request that should be cancelled!!!");
[self.delegate searchListLoader:self didLoadGlobalSearchList:searchListItems];
}
Sometimes I hit the assertion, so I investigated a bit and discovered that most of the times
the failure block is called with an error code NSURLErrorCancelled which is the expected behavior but sometimes, the success block is called!
This stack trace tells me that things in my code occurred in the right order
2013-12-22 09:38:46.484 Point[63595:a0b] <PointSearchRequest: 0x18202b50> started
2013-12-22 09:38:46.486 Point[63595:a0b] <PointSearchRequest: 0x18202b50> cancelled
2013-12-22 09:38:46.487 Point[63595:a0b] <PointSearchRequest: 0x181a5dd0> started
2013-12-22 09:38:46.496 Point[63595:a0b] *** Assertion failure in -[SearchListLoader request:didLoadSearchListItems:], /Users/aurelienporte/Documents/Developpement/Perso/iOS/Point/Point/Classes/Models/SearchListLoader.m:82
(lldb) po request
<PointSearchRequest: 0x18202b50>
Plus, I looked at the property isCancelled on AFHTTPRequestOperation when success block is called but it gives me NO (!!!)
I know I could end up just testing instead of using NSAssert but would like to find the origin of the problem.
Have you ever encountered similar issues where cancel does not actually cancel the request? And then the success block is called instead of the failure block? Is this an issue to report to AFNetworking team? Thanks!
If it can help, the requests are freaking fast (Google autocomplete aPI is impressive...)
Looking into AFNetworkingCode, see AFURLConnectionOperation.m, line 461
- (void)cancel {
[self.lock lock];
if (![self isFinished] && ![self isCancelled]) {
[super cancel];
if ([self isExecuting]) {
[self performSelector:#selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
}
[self.lock unlock];
}
It seems that the only possibility for the race condition you are seeing is the case when the operation has already been finished ([self isFinished]). Note that the interval between your cancel and completion block is very small (10 ms). Maybe you could check isFinished before trying to cancel the request?
As we all know, a thread function will go no although you cancel the thread in the midway, when it start, it will complete the method. So as the request. That is, if the request is still in the NSOperationQueue, you can cancel it, but as long as you summit the operation,and the block is copy,it will call back.
If you don't want to accept the call back, you can just in the call back to tell whether the request is canceled.
Hope that it will help you.

Serializing asynchronous tasks in objective C

I wanted to be able to serialize 'genuinely' async methods, for example:
making a web request
showing a UIAlertView
This is typically a tricky business and most samples of serial queues show a 'sleep' in an NSBlockOperation's block. This doesn't work, because the operation is only complete when the callback happens.
I've had a go at implementing this by subclassing NSOperation, here's the most interesting bits of the implementation:
+ (MYOperation *)operationWithBlock:(CompleteBlock)block
{
MYOperation *operation = [[MYOperation alloc] init];
operation.block = block;
return operation;
}
- (void)start
{
[self willChangeValueForKey:#"isExecuting"];
self.executing = YES;
[self didChangeValueForKey:#"isExecuting"];
if (self.block) {
self.block(self);
}
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
self.executing = NO;
self.finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (BOOL)isFinished
{
return self.finished;
}
- (BOOL) isExecuting
{
return self.executing;
}
This works well, here's a demonstration...
NSOperationQueue *q = [[NSOperationQueue alloc] init];
q.maxConcurrentOperationCount = 1;
dispatch_queue_t queue = dispatch_queue_create("1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("2", NULL);
MYOperation *op = [MYOperation operationWithBlock:^(MYOperation *o) {
NSLog(#"1...");
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(#"1");
[o finish]; // this signals we're done
});
}];
MYOperation *op2 = [MYOperation operationWithBlock:^(MYOperation *o) {
NSLog(#"2...");
dispatch_async(queue2, ^{
[NSThread sleepForTimeInterval:2];
NSLog(#"2");
[o finish]; // this signals we're done
});
}];
[q addOperations:#[op, op2] waitUntilFinished:YES];
[NSThread sleepForTimeInterval:5];
Note, I also used a sleep but made sure these were executing in background thread to simulate a network call. The log reads as follows
1...
1
2...
2
Which is as desired. What is wrong with this approach? Are there any caveats I should be aware of?
"Serializing" asynchronous tasks will be named actually "continuation" (see also this wiki article Continuation.
Suppose, your tasks can be defined as an asynchronous function/method with a completion handler whose parameter is the eventual result of the asynchronous task, e.g.:
typedef void(^completion_handler_t)(id result);
-(void) webRequestWithCompletion:(completion_handler_t)completionHandler;
-(void) showAlertViewWithResult:(id)result completion:(completion_handler_t)completionHandler;
Having blocks available, a "continuation" can be easily accomplished through invoking the next asynchronous task from within the previous task's completion block:
- (void) foo
{
[self webRequestWithCompletion:^(id result) {
[self showAlertViewWithResult:result completion:^(id userAnswer) {
NSLog(#"User answered with: %#", userAnswer);
}
}
}
Note that method foo gets "infected by "asynchrony" ;)
That is, here the eventual effect of the method foo, namely printing the user's answer to the console, is in fact again asynchronous.
However, "chaining" multiple asynchronous tasks, that is, "continuing" multiple asynchronous tasks, may become quickly unwieldy:
Implementing "continuation" with completion blocks will increment the indentation for each task's completion handler. Furthermore, implementing a means to let the user cancel the tasks at any state, and also implement code to handle the error conditions, the code gets quickly confusing, difficult to write and difficult to understand.
A better approach to implement "continuation", as well as cancellation and error handling, is using a concept of Futures or Promises. A Future or Promise represents the eventual result of the asynchronous task. Basically, this is just a different approach to "signal the eventual result" to the call site.
In Objective-C a "Promise" can be implemented as an ordinary class. There are third party libraries which implement a "Promise". The following code is using a particular implementation, RXPromise.
When utilizing such a Promise, you would define your tasks as follows:
-(Promise*) webRequestWithCompletion;
-(Promise*) showAlertViewWithResult:(id)result;
Note: there is no completion handler.
With a Promise, the "result" of the asynchronous task will be obtained via a "success" or an "error" handler which will be "registered" with a then property of the promise. Either the success or the error handler gets called by the task when it completes: when it finishes successfully, the success handler will be called passing its result to the parameter result of the success handler. Otherwise, when the task fails, it passes the reason to the error handler - usually an NSError object.
The basic usage of a Promise is as follows:
Promise* promise = [self asyncTasks];
// register handler blocks with "then":
Promise* handlerPromise = promise.then( <success handler block>, <error handler block> );
The success handler block has a parameter result of type id. The error handler block has a parameter of type NSError.
Note that the statement promise.then(...) returns itself a promise which represents the result of either handler, which get called when the "parent" promise has been resolved with either success or error. A handler's return value may be either an "immediate result" (some object) or an "eventual result" - represented as a Promise object.
A commented sample of the OP's problem is shown in the following code snippet (including sophisticated error handling):
- (void) foo
{
[self webRequestWithCompletion] // returns a "Promise" object which has a property "then"
// when the task finished, then:
.then(^id(id result) {
// on succeess:
// param "result" is the result of method "webRequestWithCompletion"
return [self showAlertViewWithResult:result]; // note: returns a promise
}, nil /*error handler not defined, fall through to the next defined error handler */ )
// when either of the previous handler finished, then:
.then(^id(id userAnswer) {
NSLog(#"User answered with: %#", userAnswer);
return nil; // handler's result not used, thus nil.
}, nil)
// when either of the previous handler finished, then:
.then(nil /*success handler not defined*/,
^id(NEError* error) {
// on error
// Error handler. Last error handler catches all errors.
// That is, either a web request error or perhaps the user cancelled (which results in rejecting the promise with a "User Cancelled" error)
return nil; // result of this error handler not used anywhere.
});
}
The code certainly requires more explanation. For a detailed and a more comprehensive description, and how one can accomplish cancellation at any point in time, you may take a look at the RXPromise library - an Objective-C class which implements a "Promise". Disclosure: I'm the author of RXPromise library.
At a first glance this would work, some parts are missing to have a "proper" NSOperation subclass though.
You do not cope with the 'cancelled' state, you should check isCancelled in start, and not start if this returns YES ("responding to the cancel command")
And the isConcurrent method needs to be overridden too, but maybe you omitted that for brevity.
When subclassing NSOperation I would strongly suggest only overriding main unless you really know what you are doing as it is really easy to mess up thread safety. While the documentation says that the operation will not be concurrent the act of running them through an NSOperationQueue automatically makes them concurrent by running them on a separate thread. The non-concurrency note only applies if you call the start method of the NSOperation yourself. You can verify this by noting the thread ID that each NSLog line contains. For example:
2013-09-17 22:49:07.779 AppNameGoesHere[58156:ThreadIDGoesHere] Your log message goes here.
The benefit of overriding main means that you don't have to deal with thread safety when changing the state of the operation NSOperation handles all of that for you. The main thing that is serializing your code is the line that sets maxConcurrentOperationCount to 1. This means each operation in the queue will wait for the next to run (all of them will run on a random thread as determined by the NSOperationQueue). The act of calling dispatch_async inside each operation also triggers yet another thread.
If you are dead set on using subclassing NSOperation then only override main, otherwise I would suggest using NSBlockOperation which seems like what you are somewhat replicating here. Really though I would avoid NSOperation altogether, the API is starting to show its age and is very easy to get wrong. As an alternative I would suggest something like RXPromise or my own attempt at solving this problem, FranticApparatus.

how do I debug a wait_fences error

wait_fences: failed to receive reply: 10004003
I continue to get this error. It happens when my app starts up. How would I debug this issue. Is there some way I can watch the wait_fences and see what is going on there. Is there some way to see what threads are blocking what ?
It feels like this error is nothing but a theading black hole with no tools or info getting into or coming out of the void.
Anyone with tips on debugging this error would be greatly appreciated.
New
I have now changed my threading, All threading calls go through this method set to do all their dispatches
NOTE: Also I wish you guys would not vote to close my question just because you have seen another question about this problem. There is no real information about this error. I need to know what causes this error and/or how to debug it. not the common "add the super calls to your viewDidAppear and the such. If those helped, I would not have asked this question.
+ (void) ensureDispatchOfBlock:(dispatch_block_t) block onQueue:(dispatch_queue_t) queue async:(BOOL) async{
if (dispatch_get_current_queue() == queue){
block();
}
else {
if (async){
dispatch_async(queue, block);
}
else {
dispatch_sync(queue, block);
}
}
}
+ (void) ensureDispatchOnMainThread:(dispatch_block_t) block async:(BOOL) async{
[self ensureDispatchOfBlock:block onQueue:dispatch_get_main_queue() async:async];
}
+ (BOOL) addBlock:(dispatch_block_t) block toQueue:(dispatch_queue_t) queue async:(BOOL) async {
if (!async && dispatch_get_current_queue() == queue){
return NO;
}
if (async){
dispatch_async(queue, block);
}
else {
dispatch_sync(queue, block);
}
return YES;
}
That error usually comes up as a result of trying to animate something that isn't visible on-screen. If it happens on launch, I'd guess that you're trying to start an animation in viewDidLoad.
I'd look for missing calls to super's view(Did/Will)Appear. If these are omitted you will see "wait fences", whatever that means.

Resources