Processing next, complete and error in ReactiveCocoa
I'm still pretty new to the ReactiveCocoa world and I just wanted this general scenario to be clarified. I noticed that other people are struggling with this case on GitHub and SO, but I am still not getting a proper answer.
The following example works, but I saw Justin Summers say that subscriptions - within a subscription or subscriptions in general can be code smells. Therefore, I want to try to avoid bad habits while learning this new paradigm.
So the example (using MVVM) is pretty simple:
- ViewController contains a login button that is connected to the login command in view mode
- The ViewModel specifies the command action and simulates some network request for this example.
- The ViewController subscribes to the executeSignals command and can differentiate between three return types: 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 it in the more active ReactiveCococa mode?
source to share
As it works RACCommand
, values ββcome from signal executionSignals
, errors from signal errors
and end up with, well, that's where to use -materialize
and -dematerialize
like in your example.
In the above example, the login may not need to be completed to simulate it. Instead, a boolean signal can be defined as binary in behavior: it either sends @YES
(for example) or sends an error message. Under these conditions, the code will look like this:
[[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 at odds with the typical subscribeNext:error:completed:
RAC pattern . It has to do with the API RACCommand
.
Note that the operator has -concat
been applied to executionSignals
to align internal values ββand avoid internal signatures. You can also see -flatten
or -switchToLatest
used in other examples RACCommand
, but whenever a command has a property allowsConcurrentExecution
set to NO
(which is the default), execution is serialized, making a -concat
statement that naturally matches and expresses that consistent semantics. Applying -flatten
or -switchToLatest
will indeed work as they degenerate to -concat
when applied to sequential cues, but they express semantics to the reader that doesn't apply.
source to share