32

In ReactiveCocoa, if we chaining several dependent signals, we must use subscribeNext: for the next signal in the chain to receive the value previous signal produced (for example, a result of an asynchronous operation). So after a while, the code turns into something like this (unnecessary details are omitted):

RACSignal *buttonClickSignal = [self.logIn rac_signalForControlEvents:UIControlEventTouchUpInside];

[buttonClickSignal subscribeNext:^(UIButton *sender) {    // signal from a button click
    // prepare data

    RACSignal *loginSignal = [self logInWithUsername:username password:password];    // signal from the async network operation

    [loginSignal subscribeNext:^void (NSDictionary *json) {
        // do stuff with data received from the first network interaction, prepare some new data

        RACSignal *playlistFetchSignal = [self fetchPlaylistForToken:token];         // another signal from the async network operation

        [playlistFetchSignal subscribeNext:^(NSDictionary *json) {
            // do more stuff with the returned data
        }];

        // etc
    }];
}];

This ever-increasing nesting does not look much better than the non-reactive example that is given in the documentation:

[client logInWithSuccess:^{
    [client loadCachedMessagesWithSuccess:^(NSArray *messages) {
        [client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) {
            NSLog(@"Fetched all messages.");
        } failure:^(NSError *error) {
            [self presentError:error];
        }];
    } failure:^(NSError *error) {
        [self presentError:error];
    }];
} failure:^(NSError *error) {
    [self presentError:error];
}];

Am I missing something? Is there a better pattern of chaining dependent work in ReactiveCocoa?

Sergey Mikhanov
  • 8,350
  • 9
  • 40
  • 54

1 Answers1

48

This is when the RACStream and RACSignal operators start really coming in handy. In your particular example, you can use -flattenMap: to incorporate results into new signals:

[[[buttonClickSignal
    flattenMap:^(UIButton *sender) {
        // prepare 'username' and 'password'
        return [self logInWithUsername:username password:password];
    }]
    flattenMap:^(NSDictionary *json) {
        // prepare 'token'
        return [self fetchPlaylistForToken:token];
    }]
    subscribeNext:^(NSDictionary *json) {
        // do stuff with the returned playlist data
    }];

If you don't need the results from any step, you can use -sequenceMany: or -sequenceNext: instead for a similar effect (but for a clearer expression of intent).

Justin Spahr-Summers
  • 16,742
  • 2
  • 59
  • 78
  • 1
    This solution works for me. I wrote a [blog article](http://www.techsfo.com/blog/2013/08/managing-nested-asynchronous-callbacks-in-objective-c-using-reactive-cocoa/) that explains this approach in more detail. – Richard H Fung Aug 05 '13 at 05:06
  • @Justin, would you mind elaborating more on the proper way to do error handling along the way? – DogpatchTech Mar 01 '14 at 00:35
  • @DogpatchTech You can use `-catch:` or `-catchTo:` for this. See [this explanation](http://stackoverflow.com/questions/19439636/difference-between-catch-and-subscribeerror/19439687#19439687). – Justin Spahr-Summers Mar 03 '14 at 18:30
  • 1
    `-sequenceMany:` and `-sequenceNext:` are deprecated in 2.0+; `-sequenceMany:` is supposed to be replaced with `-flattenMap:` while ignoring the block arguments; `-sequenceNext:` is replaced by `-then:`. (See the changelog entry here: https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/CHANGELOG.md#more-obvious-sequencing-operator) – febeling May 15 '14 at 19:29
  • @JustinSpahr-Summers how would you rewrite the above code if the `fetchPlaylistForToken` should have been called after `loginWithUsername` `completed` instead of sending the `next` event? – Valerio Santinelli Mar 12 '15 at 09:00
  • 1
    @ValerioSantinelli An operator like `-then:`, or `-materialize`, or something like that. That sounds like a code smell, though. – Justin Spahr-Summers Mar 12 '15 at 16:54
  • @JustinSpahr-Summers can you use `concat`? – onmyway133 Mar 31 '15 at 09:02