1

I'm looking to clean up some code I have that has gotten out of control. There are often situations where I need to interact with a couple of remote APIs, such as Parse and Facebook, perhaps Core Data, while the user is waiting, staring at my activity indicator spin.

My requirements are:

  • everything slow must be, of course, in a background thread
  • no silent or ignored error messages.
  • I want to help both the user (so that he doesn't freak out) and myself (for when I get the support call and I need to figure out what went wrong) with as helpful error messages as possible.
  • I want the logic to be maintainable. Chunks like the one below go up in cyclomatic complexity really fast and become a nightmare to work with if not handled properly, especially with the addition of multithreading.

The pattern I use right now goes as:

- (void)sampleFacebookProcessingCall
{
    [self.spinner startAnimating];
    [FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *facebookRequestError)
    {
        // we're back in main queue, let's return to a secondary queue
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

            // don't want do display the actual error to the user
            NSString *errorMessage;
            NSError *error;
            @try {
                if (facebookRequestError) {
                    errorMessage = @"Could not connect to Facebook, please try again later.";
                    return;
                }

                [Helper someParseCall:&error];
                if (error) {
                    errorMessage = @"The operation could not be completed, please try again later. Code: FOO1";
                    return;
                }

                [Helper someOtherParseCall:&error];
                if (error) {
                    errorMessage = @"The operation could not be completed, please try again later. Code: FOO2";
                    return;
                }

                [...]
            }
            @finally {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.activityIndicator stopAnimating];
                    if (errorMessage) {
                        // there might be half-baked state out there afer the error
                        [self cleanupResources];
                        [UIHelper displayErrorAlertViewWithMessage:errorMessage];
                    }
                });
            }
         });
     }];
 }

Now, there are definitely different patterns for escaping through a flow when an error occurs. The @try/@finally pattern with returns (same idea as the old school goto with the cleanup label) is one way. Another way would be to use an error object as a "should proceed" variable and do something like:

if (!error) {
    doStuff(&error)
}
if (!error) {
[...]

The addition of GCD complicates things a bit because now you have to make sure that the heavy work is being always done in the background and that errors are only ever reported in the main thread. Every API's library does things a bit differently, so you have something like Facebook which only accepts blocks to be executed in main thread, which you have to interweave with Parse which allows you to run blocking calls if you wanted to.

I'm wondering if anybody out there has come up with a cleaner approach. I feel like this is something most apps end up dealing with sooner or later and I don't need to reinvent the wheel.

Alexandr Kurilin
  • 7,685
  • 6
  • 48
  • 76

0 Answers0