Reputation: 2926
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:
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
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