26

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];
}];
allprog
  • 16,540
  • 9
  • 56
  • 97
Ash Furrow
  • 12,391
  • 3
  • 57
  • 92

4 Answers4

28

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.

Dave Lee
  • 6,299
  • 1
  • 36
  • 36
  • 1
    Ah, that makes a lot of sense, thanks! Would an outsider to the button ever subscribe to its signal block? – Ash Furrow Oct 18 '13 at 11:34
  • 2
    Yep, absolutely. One example is if the button is used to coordinate interaction with some API, you would subscribe to the resulting signal to get the results of that API call. – Dave Lee Oct 18 '13 at 16:19
7

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.

Nikolay Kasyanov
  • 897
  • 5
  • 14
6

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.

terry lewis
  • 672
  • 1
  • 5
  • 13
  • This is a great concrete example that really illustrates how the results from the command can be used when captured in a signal and hence why returning +empty may mean the command is just using side effects. Thanks Terry! – user2067021 May 04 '16 at 01:58
2

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;
babygau
  • 1,551
  • 18
  • 27