Retrying commands with ReactiveCocoa

313 views Asked by At

I'm trying to refactor my iOS app into ReactiveCocoa and ReactiveViewModel and I'm struggling with trying to work out a couple of best practices.

I'll boil this down to a simple use case - I was to push a view controller which loads some data and shoves it into a table view. If the endpoint call fails for whatever reason, I want to display a view on screen with a retry button.

I currently have this working but it looks a bit filthy. I feel like there must be a better way - am I even doing this correctly?

In my ViewModel's init method, I'm creating my command, which is called as soon as the ViewModel becomes active.

// create the command to load the data
@weakify(self);
self.loadStationsCommand = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *(id input) {
    @strongify(self);
    return [RACSignal createSignal:^(RACDisposable *(id<RACSubscriber subscriber) {
        // load data from my API endpoint
        ...
        BOOL succeeded = ...;
        if (succeeded) {
            [subscriber sendNext:nil];
            [subscriber sendCompleted:nil];
        } else {
            // failed
            [subscriber sendError:nil];   
        }
        return nil;
    }
}];

// start the command when the ViewModel's ready
[self.didBecomeActiveSignal subscribeNext:^(id x) {
    @strongify(self);
    [self.loadStationsCommand execute:nil];
}];

In my UIViewController, I'm subscribing to the command via -

[self.viewModel.loadStationsCommand.executionSignals subscribeNext:^(RACSignal *loadStationsSignal) {
    [loadStationsSignal subscribeNext:^(id x) {
        // great, we got the data, reload the table view. 
        @strongify(self);
        [self.tableView reloadData];
    } error:^(NSError *error) {
        // THIS NEVER GETS CALLED?!
    }];
}];

[self.viewModel.loadStationsCommand.errors subscribeNext:^(id x) {
    // I actually get my error here. 
    // Show view/popup to retry the endpoint.
    // I can do this via [self.viewModel.loadStationsCommand execute:nil]; which seems a bit dirty too.
}];

I must have some misunderstanding as to how RACCommand works, or at the very least I feel I'm not doing this as cleanly as I can.

Why doesn't the error block on my loadStationsSignal get called? Why do I need to subscribe to executionCommand.errors instead?

Is there a better way?

1

There are 1 answers

0
Michał Ciuba On BEST ANSWER

It is a correct way to handle errors with RACCommand. As you can read in the docs, errors of the inner signal are not sent when using executionSignals:

 Errors will be automatically caught upon the inner signals, and sent upon
 `errors` instead. If you _want_ to receive inner errors, use -execute: or
 -[RACSignal materialize].

You can also use RAC additions to UIButton and bind self.viewModel.loadStationsCommand to rac_command of the retry button.

There is a good article which explains RACCommand and shows some interesting patterns to be used with it.