AWS Mobile Blog

Using the AWS SDK for iOS Asynchronously – Part IV: Grand Central Dispatch (GCD) Best Practices

by Yosuke Matsuda | on | in S3 | Permalink | Comments |  Share

Version 2 of the AWS Mobile SDK

  • This article and sample apply to Version 1 of the AWS Mobile SDK. If you are building new apps, we recommend you use Version 2. For details, please visit the AWS Mobile SDK page.
  • This content is being maintained for historical reference.

In a previous post, you leaned how to use GCD to turn synchronous AWS SDK for iOS operations into asynchronous ones. As with our previous posts in this series, we present some best practices for using GCD with the SDK in order to avoid some common mistakes.

Avoid using AmazonServiceRequestDelegate in GCD blocks

It is possible to use AmazonServiceRequestDelegate with GCD. However, it requires deep understanding of run loops and manual handling of them. As a general guideline, we recommend not using delegates with GCD. In many cases, using AmazonServiceRequestDelegate without GCD will be more suitable.

Here is a simple example that WON’T work although it looks like a valid request call. Even if you adopt the AmazonServiceRequestDelegate protocol and implement its delegate methods, those methods will not be called. Can you see what’s causing the issue?

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{

    S3PutObjectRequest *request = [[S3PutObjectRequest alloc] initWithKey:objectKey inBucket:bucketName];
    request.data = data;
    request.delegate = self;

    [s3Client putObject:request];
});

The block in this GCD queue will be executed in the background thread, which has its own run loop, separate from the one on the main thread. The delegate will be registered with the background thread’s run loop that is responsible for calling the AmazonServiceRequestDelegate method. However, putObject: immediately returns and the entire block also returns. When the block finishes its execution, the background thread will be collected by GCD, and nothing that is responsible for calling the delegate methods will be left behind. That’s why the above code sample doesn’t work.

Avoid using dispatch_sync on the main thread as much as possible

If you call the following on the main thread, it will cause a deadlock. Essentially what the code is doing is waiting for a block to finish its execution on the main thread while you are blocking the main thread. At this moment, the app has only one fate: it will be killed by the watchdog.

dispatch_sync(dispatch_get_main_queue(), ^{
    ...
});

Even when using concurrent or serial queues, be careful when you use dispatch_sync on the main thread. The main thread will be blocked at least the amount of time it takes to complete the long running process in the block. It kills one of the most important benefits of executing a long running process in the background.

// The main thread will be blocked until the long running process completes.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{

    // Some long running process.
});

No exceptions in GCD

GCD doesn’t catch exceptions, so you must either not throw exceptions or catch all exceptions in the block.

The AWS SDK for iOS by default throws exceptions; however, we provide a configuration option to return an NSError object with the response object instead of throwing an NSException object. In order to turn on the option, please refer to this blog post.

Alternatively, you can catch all exceptions before returning from the block. AmazonServiceException is a subclass of AmazonClientException, so you should catch AmazonServiceException first, then AmazonClientException.

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{

    @try
    {
        S3PutObjectRequest *request = [[S3PutObjectRequest alloc] initWithKey:objectKey inBucket:bucketName];
        request.data = data;

        S3PutObjectResponse *response = [s3Client putObject:request];
        // Do something with response.
    }
    @catch (AmazonServiceException *serviceException) {
        // Handle serviceException.
    }
    @catch (AmazonClientException *clientException) {
        // Handle clientException.
    }
});

No progress bars available

The synchronous calls of the AWS SDK are not capable of providing progress feedback. Although we can make synchronous calls asynchronously with GCD, you still don’t get progress feedback. If you want to create a progress bar, please use the AmazonServiceRequestDelegate method covered in the previous blog post.

As always, please leave a comment below if you have questions on this matter.

Further reading: