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

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.

Related

RACCommand separate handling of successful completion

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.

m7/m8 detect if motion activity authorization was denied

I am using M7/M8 chip's MotionActivity in a variety of ways, including for step counting. For step counting, I both query for the day's steps, and request ongoingly the step count as they occur realtime.
Currently before I do this I check [CMStepCounter isStepCountingAvailable], as well as a local override flag, before proceeding with this code. I assumed isStepCountingAvailable would return FALSE if authorization for motionActivity was not granted. This does not seem to be the case, it appears to be more of a hardware detection only. I cannot seem to find other methods that detect whether authorization was granted for this.
What this means is that startStepCountingUpdatesToQueue and queryStepCountStartingFrom both run, and return blocks, but always return an error code. Specifically CMErrorDomain code 105.
Is there a better way for me to determine if motionActivity has not been authorized? I've got some fallback code but I'd prefer a boolean check beforehand, instead of an error code in the return block.
if (self.useM7IfAvailable && [CMStepCounter isStepCountingAvailable]){
self.cmStepCounter = [[CMStepCounter alloc] init];
[self.cmStepCounter startStepCountingUpdatesToQueue:self.operationQueue updateOn:1.0 withHandler:^(NSInteger numberOfSteps, NSDate *timestamp, NSError *error){
if(!error){
// do something with numberOfSteps
} else {
// not authorized: CMErrorDomain code 105
}
}];
}
[self.cmStepCounter queryStepCountStartingFrom:dayStart to:dayEnd toQueue:_operationQueue withHandler:^(NSInteger numberOfSteps, NSError *error) {
if(!error){
// do something with numberOfSteps
} else {
// not authorized: CMErrorDomain code 105
}
}];
You're doing it correctly by checking for the error. Per the docs (https://developer.apple.com/library/ios/documentation/coremotion/reference/cmmotionmanager_class/index.html#//apple_ref/c/tdef/CMError) you'll receive back CMErrors with Error Code 105 like you've seen.
Unfortunately there's no way to check ahead-of-time to see if you're authorized or not, but this follows Apple's paradigms with other Core-level frameworks that require authorization, like CoreLocation. The reasoning is you can be in the middle of getting motion steps while in the background, and the user can then disable your motion access, which you'll have to react to that event in probably the same way you'd react to not being authorized in the first place.

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.

How can I wait for a NSURLConnection delegate to finish before executing the next statement?

This has been a hard one to search.
I found a similar question, iOS 5 Wait for delegate to finish before populating a table?, but the accepted answer was 'Refresh the table view,' and that does not help me. The other results I found tended to be in c#.
I have an app that streams from iPhone to Wowza servers. When the user hits record, I generate a unique device id, then send it to a PHP script on the server that returns a JSON document with configuration settings (which includes the rtmp dump link).
The problem is, the delegate methods are asynchronous, but I need to get the config settings before the next lines of code in my - (IBAction)recordButtonPressed method, since that code is what sets the profile settings, and then records based on those settings.
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
Is there not some simple waitUntilDelegateIsFinished:(BOOL)nonAsyncFlag flag I can send to the delegator so I can have sequential operations that pull data from the web?
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
You have analyzed and understood the situation and you have described its possible solutions perfectly. I just don't agree with your conclusions. This kind of thing happens all the time:
- (void) doPart1 {
// do something here that will eventually cause part2 to be called
}
- (void) doPart2 {
}
You can play various games with invocations to make this more elegant and universal, but my advice would be, don't fight the framework, as what you're describing is exactly the nature of being asynchronous. (And do not use a synchronous request on the main thread, since that blocks the main thread, which is a no-no.)
Indeed, in an event-driven framework, the very notion "wait until" is anathema.
Why not to use synchronous request?
Wrap your asynchronous NSURLConnection request in a helper method which has a completion block as a parameter:
-(void) asyncDoSomething:(void(^)(id result)completionHandler ;
This method should be implemented in the NSURLConnectionDelegate. For details see the example implementation and comments below.
Elsewhere, in your action method:
Set the completion handler. The block will dispatch further on the main thread, and then perform anything appropriate to update the table data, unless the result was an error, in which case you should display an alert.
- (IBAction) recordButtonPressed
{
[someController asyncConnectionRequst:^(id result){
if (![result isKindOfClass:[NSError class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
// We are on the main thread!
someController.tableData = result;
});
}
}];
}
The Implementation of the method asyncConnectionRequst: could work as follows: take the block and hold it in an ivar. When it is appropriate call it with the correct parameter. However, having blocks as ivars or properties will increase the risk to inadvertently introduce circular references.
But, there is a better way: a wrapper block will be immediately dispatched to a suspended serial dispatch queue - which is hold as an ivar. Since the queue is suspended, they will not execute any blocks. Only until after the queue will be resumed, the block executes. You resume the queue in your connectionDidFinish: and connectionDidFailWithError: (see below):
In your NSURLConnectionDelegate:
-(void) asyncConnectionRequst:(void(^)(id result)completionHandler
{
// Setup and start the connection:
self.connection = ...
if (!self.connection) {
NSError* error = [[NSError alloc] initWithDomain:#"Me"
code:-1234
userInfo:#{NSLocalizedDescriptionKey: #"Could not create NSURLConnection"}];
completionHandler(error);
});
return;
}
dispatch_suspend(self.handlerQueue); // a serial dispatch queue, now suspended
dispatch_async(self.handlerQueue, ^{
completionHandler(self.result);
});
[self.connection start];
}
Then in the NSURLConnectionDelegate, dispatch a the handler and resume the
handler queue:
- (void) connectionDidFinishLoading:(NSURLConnection*)connection {
self.result = self.responseData;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
Likewise when an error occurred:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.result = error;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
There are even better ways, which however involve a few more basic helper classes which deal with asynchronous architectures which at the end of the day make your async code look like it were synchronous:
-(void) doFourTasksInAChainWith:(id)input
{
// This runs completely asynchronous!
self.promise = [self asyncWith:input]
.then(^(id result1){return [self auth:result1]);}, nil)
.then(^(id result2){return [self fetch:result2];}, nil)
.then(^(id result3){return [self parse:result3];}, nil)
.then(^(id result){ self.tableView.data = result; return nil;}, ^id(NSError* error){ ... })
// later eventually, self.promise.get should contain the final result
}

Resources