drhr
drhr

Reputation: 2281

What is the best way to combine RACCommands into a common result?

Using ReactiveCocoa 2.0, is there a better way to do the following, without having to materialize/dematerialize and still being able to capture errors from any of the 3 signals, without duplicating code?

There are 3 login buttons. Each returns a signal corresponding to an asynchronous "login" API call. Once those finish, they return user objects, errors, and/or completion.

// Login signals
_loginButton.rac_command = [[RACCommand alloc] initWithEnabled:loginValid signalBlock:^RACSignal *(id input) {
    return [[API doLogin:_usernameField.text password:_passwordField.text] materialize];
}];
_fbLoginButton.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    return [[API doFacebookLogin] materialize];
}];
_twLoginButton.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    return [[API doTwitterLogin] materialize];
}];

// Login response from any of the 3 signals
[[RACSignal
  merge:@[_loginButton.rac_command.executionSignals,
          _fbLoginButton.rac_command.executionSignals,
          _twLoginButton.rac_command.executionSignals]]
 subscribeNext:^(RACSignal *loginSignal) {
     RACSignal * s = [loginSignal dematerialize];
     [s subscribeNext:^(User *x) {
        NSLog(@"user: %@", x);
     } error:^(NSError *error) {
        NSLog(@"error: %@", error);
     } completed:^{
        NSLog(@"Completed.");
     }];
 }];

Upvotes: 2

Views: 1935

Answers (1)

Justin Spahr-Summers
Justin Spahr-Summers

Reputation: 16973

Since errors are automatically diverted to the errors signal, you normally don't have to deal with materialization or any of that yourself. In fact, that (potential) complexity was the original motivation for the special behavior of errors.

Just merge the error signals and deal with them in one place:

[[RACSignal
    merge:@[
        _loginButton.rac_command.errors,
        _fbLoginButton.rac_command.errors,
        _twLoginButton.rac_command.errors,
    ]]
    subscribeNext:^(NSError *error) {
        NSLog(@"error: %@", error);
    }];

As a sidenote, you can also use -flatten — instead of an inner subscription — to simplify the handling of login responses:

[[[RACSignal
    merge:@[
        _loginButton.rac_command.executionSignals,
        _fbLoginButton.rac_command.executionSignals,
        _twLoginButton.rac_command.executionSignals,
    ]]
    // Flattens the signal of `User` signals by one level. The result is
    // one signal of `User`s.
    //
    // This avoids any need for an inner subscription.
    flatten]
    subscribeNext:^(User *x) {
        // This means that a login request completed as well, so there's no need
        // for a separate `completed` block.
        NSLog(@"user: %@", x);
    }];

Upvotes: 8

Related Questions