Steffen D. Sommer
Steffen D. Sommer

Reputation: 2926

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:

  1. A ViewController contains a login button which is connected to a login command in the viewmodel
  2. The ViewModel specifies the command action and simulates some network request for this example.
  3. 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?

Upvotes: 4

Views: 2232

Answers (1)

Dave Lee
Dave Lee

Reputation: 6489

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.

Upvotes: 9

Related Questions