RACSignal doNext with signals - ios

I need to be able to use doNext with a signal inside, not just a block. The problem is that I use signals for side effects too.
When I do this kind of things my code looks really messy.
For example:
[httpRequestSignal flattenMap:^(Response *response) {
// Save the response.
return [[self saveResponse:response] then:^{
// Return the response.
return [RACSignal return:response];
}];
}];
I'm tempted to use first and make the code look much better, but it looks wrong making a signal synchronous.
[httpRequestSignal doNext:^(Response *response) {
// Save the response.
[[self saveResponse:response] first];
}];
Any advice?

Related

wrap & unwrap with NSValue

In my project, I have a function like this:
- (void)doSomething:(NSError**)error {
...
}
I need to call this function on another thread by using function performSelector:onThread:withObject:waitUntilDone: , something like this:
[self performSelector:#selector(doSomething:) onThread:anotherThread withObject:??? waitUntilDone:NO];
But the function parameter is of type NSError**. I am considering refactor the parameter type of function -(void)doSomething: from NSError** to NSValue* and pass NSValue* type as argument.
Which means, I need to wrap the &error (which is of type NSError **) into a NSValue and pass it as argument, and unwrap it later. How to wrap & unwrap NSError** with NSValue class?
I think you can use NSValue's valueWithPointer: and pointerValue. But I would suggest you use something else, like GCD to run a block asynchronously instead of changing your method's signature to fit the limitations of performSelector:
dispatch_async(anotherQueue, ^{
[self doSomething:&error];
});
Also this question has a few more ideas on how to approach this problem if you really want to go down that path.
You need to rethink your approach to this problem. Your method:
- (void)doSomething:(NSError**)error
follows the standard Objective-C pattern of passing the address of an NSError * variable so that the method can set the variable to return the error.
If you try to call this method asynchronously, whether with performSelector:onThread:withObject:waitUntilDone: as you are attempting or using GCD (as Felipe Cypriano has also suggested), you have to be careful - the variable whose address you pass must exist as the time the async call is executed, and even after you've addressed that you have to figure out when the async call has finished so you can check if it has set the variable...
A common way to deal with issues like this is to use a completion block which the async method calls when it is finished, passing on any results - an NSError * in your case. For example you could write a method:
- (void) doSomethingAsyncAndThen:(void (^)(NSError *))completionBlock
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0),
^{
NSError *error = nil;
[self doSomething:&error];
completionBlock(error);
});
}
and call it like:
[self doSomethingAsyncAndThen:^(NSError *error) { NSLog(#"error: %#", error); }];
though you will want to do something other than just NSLog the result.
HTH

Retrying commands with ReactiveCocoa

I'm trying to refactor my iOS app into ReactiveCocoa and ReactiveViewModel and I'm struggling with trying to work out a couple of best practices.
I'll boil this down to a simple use case - I was to push a view controller which loads some data and shoves it into a table view. If the endpoint call fails for whatever reason, I want to display a view on screen with a retry button.
I currently have this working but it looks a bit filthy. I feel like there must be a better way - am I even doing this correctly?
In my ViewModel's init method, I'm creating my command, which is called as soon as the ViewModel becomes active.
// create the command to load the data
#weakify(self);
self.loadStationsCommand = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *(id input) {
#strongify(self);
return [RACSignal createSignal:^(RACDisposable *(id<RACSubscriber subscriber) {
// load data from my API endpoint
...
BOOL succeeded = ...;
if (succeeded) {
[subscriber sendNext:nil];
[subscriber sendCompleted:nil];
} else {
// failed
[subscriber sendError:nil];
}
return nil;
}
}];
// start the command when the ViewModel's ready
[self.didBecomeActiveSignal subscribeNext:^(id x) {
#strongify(self);
[self.loadStationsCommand execute:nil];
}];
In my UIViewController, I'm subscribing to the command via -
[self.viewModel.loadStationsCommand.executionSignals subscribeNext:^(RACSignal *loadStationsSignal) {
[loadStationsSignal subscribeNext:^(id x) {
// great, we got the data, reload the table view.
#strongify(self);
[self.tableView reloadData];
} error:^(NSError *error) {
// THIS NEVER GETS CALLED?!
}];
}];
[self.viewModel.loadStationsCommand.errors subscribeNext:^(id x) {
// I actually get my error here.
// Show view/popup to retry the endpoint.
// I can do this via [self.viewModel.loadStationsCommand execute:nil]; which seems a bit dirty too.
}];
I must have some misunderstanding as to how RACCommand works, or at the very least I feel I'm not doing this as cleanly as I can.
Why doesn't the error block on my loadStationsSignal get called? Why do I need to subscribe to executionCommand.errors instead?
Is there a better way?
It is a correct way to handle errors with RACCommand. As you can read in the docs, errors of the inner signal are not sent when using executionSignals:
Errors will be automatically caught upon the inner signals, and sent upon
`errors` instead. If you _want_ to receive inner errors, use -execute: or
-[RACSignal materialize].
You can also use RAC additions to UIButton and bind self.viewModel.loadStationsCommand to rac_command of the retry button.
There is a good article which explains RACCommand and shows some interesting patterns to be used with it.

Avoiding nested blocks with asynchronous code in objective-c

I have a long series of events that needs to happen in my Objective-C code. Lets say I have 6 things - thingA, thingB, thingC, thingD, thingE and thingF. thingB and thingD return a BOOL. If thingB is NO, then thingC doesn't need to be called. If thingD is NO, then thingE doesn't need to be called.
- (void)doThings:(void(^)())completion {
[self thingA: ^{
[self thingB: ^(BOOL success) {
if (success) {
[self thingC: ^{
[self thingD: ^(BOOL success) {
if (thingD) {
[self thingE: ^{
[self thingF];
completion();
}];
return;
}
[self thingF];
completion();
}];
}];
return;
}
[self thingD: ^(BOOL success) {
if (success) {
[self thingE: ^{
[self thingF];
completion();
}];
return;
}
[self thingF];
completion();
}];
}];
}];
}
This can quickly become unwieldy. You can take the things that have different outcomes but lead back into the loop, and make them into new methods, as such:
- (void)doThings:(void(^)())completion {
[self thingA: ^{
[self attemptThingB: ^{
[self attemptThingD: ^{
[self thingF];
completion();
}]
}];
}]
}
- (void)attemptThingB:(void(^)())completion {
[self thingB: ^(BOOL success) {
if (success) {
[self thingC: {
completion();
}];
return;
}
completion();
}];
}
- (void)attemptThingD:(void(^)())completion {
[self thingD: ^(BOOL success) {
if (success) {
[self thingE: ^{
completion();
}];
return;
}
completion();
}];
}
This reduces code duplication, but is still messy and difficult to keep track of. It even results in methods with awkward names, which are really just helper methods to this particular case.
There must be a better way. Something that looks a lot like synchronous coding, but is asynchronous. The above code is difficult to read, which makes it dangerous if I ever want to add something new to the flow.
Suggestions of a better solution? Something like this?
- (void)doThings {
[self thingA];
BOOL thingBSuccess = [self thingB];
if (thingBSuccess == YES) {
[self thingC];
}
BOOL thingDSuccess = [self thingD];
if (thingDSuccess == YES) {
[self thingE];
}
[self thingF];
return;
}
The immediately evident problem with that proposed solution is that the completion handler can pass out multiple objects, whereas the return value of a block can only handle 1 object. But something with a format similar to this? It's so much cleaner and easy to maintain.
I think you've discovered dispatch groups!
there are 1000s articles on them, no need to pointlessly paste something in here,
Wait until multiple networking requests have all executed - including their completion blocks
cheers!
On a simpler level, it's possible what you're looking for is just simply "breakaway code" which is a critical part of writing, simply, tidy code. Note that this is not always the solution, but often - also, I'm not 100% clear on what you're asking. But in break away code you go like this ...
{
do something;
if ( failure, and you don't want to do any more ) return;
do some other important thing;
if ( failure of that thing, and you don't want to do any more ) return;
do yet another routine here;
if ( some other failed conditions, and you don't want to do any more ) return;
}
Here's a typical example. Often you will see code something like this...
-(void)loadNameInToTheCell
{
if ( self.showname != nil && [self.showname notLike:#"blah"] && kount<3 )
{
// many many lines of code here
// many many lines of code here
// many many lines of code here
}
}
You with me? But it's much better to write it like this:
-(void)loadNameInToTheCell
{
if ( self.showname == nil ) return;
if ( [self.showname notLike:#"blah"] return;
if ( kount<3 ) return;
// many many lines of code here
// many many lines of code here
// many many lines of code here
}
makes sense?
Note that critically, in any real project it's about the documentation not the code, so you can properly discuss and comment on that ...
-(void)loadNameInToTheCell
{
if ( self.showname == nil ) return;
// don't forget Steve's routine could return nil
if ( [self.showname notLike:#"blah"] return;
// should we worry about caps here?
if ( kount<3 ) return;
// client wants to change this to 4 in future - Steve
// screw that ... Biff, 8/2014
// many many lines of code here
// many many lines of code here
// many many lines of code here
}
Makes sense right? I hope that helps with what you were asking. You have to think the "other way around" you know?
A possible rule of thumb is if you ever have a condition and then a "very long block of code" - it's sort of wrong. Turn it around the other way and have breakaway conditions. Only then, have "the actual relevant code". In a sense .. never put a long slab of important code inside an if block; you're sort of thinking the wrong way around if so.

ReactiveCocoa - Changing side effects into signals

In my application I have a signal which triggers some asynchronous network activity via flattenMap. I want to display a loading indicator while the network activity is in progress.
My current solution works just fine:
[[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
doNext:^(id x) {
// show the loading indicator as a side-effect
self.loadingIndicator.hidden = NO;
}]
flattenMap:^id(id x) {
return [self doSomethingAsync];
}]
subscribeNext:^(NSNumber *result) {
// hide the indicator again
self.loadingIndicator.hidden = YES;
// do something with the results
}];
This works, however I would like to change the above code so that the hidden property of the loading indicator can be set via a signal.
Is this possible?
Elsewhere in my app I have more complex requirements where the visibility of an element depends on a few different 'events', being able to compose these via signals would be much better.
RACCommand is tailor-built for exactly this use case, and will usually result in dramatically simpler code than the alternatives:
#weakify(self);
RACCommand *signInCommand = [[RACCommand alloc] initWithSignalBlock:^(id _) {
#strongify(self);
return [self doSomethingAsync];
}];
self.signInButton.rac_command = signInCommand;
// Show the loading indicator while signing in.
RAC(self.loadingIndicator, hidden) = [signInCommand.executing not];
It looks like your signal is: when signInButtonSignal or resultSignal send a value, invert the last value of hidden. That's easy enough.
[[[hiddenSig replayLast] not] sample:[RACSignal merge:#[signInButtonSignal, resultSignal]];
I'm using exactly that construct for a situation similar to yours. It might be nice to wrap it up into an operator:
- (RACSignal *)toggle:(RACSignal *)toggler
{
return [[[self replayLast] not] sample:toggler];
}
Then you have just
[hiddenSig toggle:[RACSignal merge:#[signInButtonSignal, resultSignal]]];
Another possibility might be a class method, tying the state to a mapping Block:
+ (RACSignal *)toggle:(RACSignal *)toggler initially:(BOOL)initial
{
__block BOOL currVal = initial;
return [[toggler map:^id (id _) {
currVal = !currVal;
return #(currVal);
}] startWith:#(initial)];
}
and then
[RACSignal toggle:[RACSignal merge:#[signInButtonSignal, resultSignal]]
initially:NO];
The answer from Josh helped quite a bit, but in the end I found a simpler solution. Simply breaking the pipeline into two signals, one for the button press, the other for the subsequent asynchronous activity. I then merged the two to give a signal which I used to bind to the loadingIndicator's hidden property:
// a signal that triggers sign-in
RACSignal *signInStartSignal = [self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside];
// a signal that emits the sign in result
RACSignal *signInResultSignal =
[signInStartSignal
flattenMap:^id(id x) {
return [self doSomethingAsync];
}];
[signInResultSignal
subscribeNext:^(NSNumber *result) {
// do something based on the result
}];
// merge the two signals
RACSignal *signInInProgress =
[[RACSignal merge:#[signInResultSignal, signInStartSignal]]
map:^id(id value) {
// if the signal value is a UIButton, the signal that
// just fired was the signInStartSignal
return #(![[value class] isSubclassOfClass:[UIButton class]]);
}];
RAC(self.signInFailureText,hidden) = signInInProgress;

Why Does RACCommand's block return a signal?

I've been learning a lot about ReactiveCocoa but one thing still puzzles me: why does the signal block on RACCommand return a signal itself?
I understand the use cases of RACCommand, its canExecute signal and signal block, and how it can be hooked up to UI elements. But what case would there be ever for returning something other than [RACSignal empty]?
infoButton.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
// Do stuff
return [RACSignal empty];
}];
There are exceptions to every rule, but generally you want all your "// Do stuff" to be captured by the returned signal. In other words, your example would be better as:
infoButton.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id input) {
return [RACSignal defer:^{
// Do stuff
return [RACSignal empty];
}];
}];
The direct benefit of this change is that, for the duration of "// Do stuff", your infoButton will be disabled, preventing it from being clicked/tapped until the returned signal has completed. In your original code, the "do stuff" is outside of the signal, and as such your button won't be disabled properly.
For work that doesn't have much latency, for example making UI changes in response to a button tap, then the enabled/disabled feature of RACCommand doesn't buy you much. But if the work is a network request, or some other potentially long running work (media processing for example), then you definitely want all of that work captured within a signal.
Imagine you have a command that should load list of items from network. You could use side effects in signal block or return a signal that would actually send these items. In the latter case you can do the following:
RAC(self, items) = [loadItems.executionSignals switchToLatest];
Also all errors sent by signal would be redirected to errors signal, so:
[self rac_liftSelector:#selector(displayError:)
withSignals:loadItems.errors, nil];
It's impossible with [RACSignal empty]-powered commands.
I have an example that might be helpful, though others might may be able to explain it better.
RACCommand Example
But basically, the way you have it with returning +empty it seems sort of pointless, as invoking the command will basically be using side effects, which we want to avoid.
Instead of using this:
infoButton.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id input) {
return [RACSignal defer:^{
// Do stuff
return nil;
}];
}];
You can use this built-in method to control the button state:
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;

Resources