Reputation: 1939
I try to serialize several asynchronous network with ReactiveCocoa:
[[[[self.profileView.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^__kindof RACSignal * _Nullable(__kindof UIControl * _Nullable value) {
@strongify(self)
return [self.viewModel signInSignal];
}] flattenMap:^__kindof RACSignal * _Nullable(id _Nullable value) {
return [self.viewModel userInfoSignal];
}] subscribeNext:^(id _Nullable x) {
} error:^(NSError * _Nullable error) {
NSLog(@"error");
}];
it works if no network error happens. However, when any error happens and the error block is called, I click the 'signInButton' again but it did not work any more. I want to know the reason and how to fix it. Thanks!
Upvotes: 0
Views: 69
Reputation: 3357
The reason is that Error
events are forwarded immediately through the chain of operators and that Error
events terminate a subscription.
One way of dealing with this is using the retry
operator which resubscribes to the signal when an error occurs. Heres a modified version of your sample using retry
:
@weakify(self);
[[[[[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^__kindof RACSignal * _Nullable(__kindof UIControl * _Nullable value) {
@strongify(self)
return [self.viewModel signInSignal];
}] flattenMap:^__kindof RACSignal * _Nullable(id _Nullable value) {
return [self.viewModel userInfoSignal];
}] doError:^(NSError * _Nonnull error) {
NSLog(@"Inner Error: %@", error);
}] retry:2]
subscribeNext:^(id _Nullable x) {
NSLog(@"Next: %@", x);
} error:^(NSError * _Nullable error) {
NSLog(@"Error: %@", error);
}];
Here, a variant with a retryCount
is used - it will retry 2 times before propagating the error to the outside. Also, doError
is used to perform a logging call as side effect when the error happens (Note: This has to be before the retry
operator)
Given the signInSignal
or userInfoSignal
will produce an error 100% of the time, 3 button presses will produce the following output
Inner Error
Inner Error
Inner Error
Error
Another solution I would suggest you take a look at is to encapsulate the logic into a RACCommand
(in your viewModel):
_signInCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [self.signInSignal flattenMap:^RACSignal *(id value) {
return self.userInfoSignal;
}];
}];
You can link this to your butto as simple as:
self.button.rac_command = self.viewModel.signInCommand;
This way, you don't need to manually handle retry: Every click on the button will invoke the command one time which can either complete successfully or error.
You can handle the side effects of the command via the commands special signals:
[[self.viewModel.signInCommand errors] subscribeNext:^(NSError * _Nullable x) {
NSLog(@"Error: %@", x);
}];
[self.viewModel.signInCommand.executionSignals subscribeNext:^(id _Nullable signal) {
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"Next: %@", x);
}];
}];
One great benefit of using RACCommand
this way is that it automatically disables the button while the command is running, so if your signInSignal
and userInfoSignal
take some time, the button will be automatically disabled in the meantime so that the user can not start the action a second time while its already running.
Upvotes: 1