AWS Mobile Blog

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

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.

Grand Central Dispatch (GCD) is a technology available on iOS that provides efficient concurrent code execution support, and the AWS SDK for iOS is fully compatible with GCD.

This post shows you how to make synchronous AWS calls asynchronously using GCD. This how-to is mainly intended for iOS developers who haven’t done any GCD programming before. We won’t discuss the details of GCD, but will focus instead on getting you started quickly with GCD. If your code already has synchronous calls on the main thread, and you are looking for a way to make them asynchronous with minimum effort, using GCD is an easy way to make the transition.

Making synchronous calls asynchronously

Suppose the following method will be called when a user taps on a button. It uploads some data to an Amazon S3 bucket. Since the touch event was generated by the UIKit, this method will be called on the main thread.

- (IBAction)upload:(id)sender
{
    ...

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

    S3PutObjectResponse *response = [s3Client putObject:request];

    ...
}

As we discussed in a previous blog post, we shouldn’t block the main thread by making synchronous networking calls on it, so we need to convert it to an asynchronous call. We can do this with GCD. It’s simple. Wrap the code you want to execute in the background into a block, and put the block into a dispatch queue.

- (IBAction)upload:(id)sender
{
    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;

        S3PutObjectResponse *response = [s3Client putObject:request];
        ...
    });
}

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) returns a global dispatch queue (concurrent queue) with the default priority that’s already available on the system. The code segment surrounded by ^{ and } is called a "block." dispatch_async(queue, ^{...} enqueues the block into the global queue for asynchronous dispatching. After that, GCD automatically dequeues the block and executes it on a background thread.

That’s all you need to do to convert the previous synchronous code into an asynchronous one. GCD will manage the execution of the blocks, and you don’t have to think about asynchronous calls in terms of "threads." GCD makes writing asynchronous code more intuitive.

Updating UI from the background thread

Now let’s think about what you need to do if you want to popup an alert to tell users that an upload to Amazon S3 has successfully completed. The following code is NOT correct.

- (IBAction)upload:(id)sender
{
    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;

        S3PutObjectResponse *response = [s3Client putObject:request];
        if(response.error == nil)
        {
            // WARNING! - Don't update user interfaces from a background thread.
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Succeeded!"
                                                            message:@"pubObject succeeded"
                                                           delegate:self
                                                  cancelButtonTitle:@"OK"
                                                  otherButtonTitles:nil];
            [alert show];

            // Do more stuff in the background thread.
        }
    });
}

This block in the global dispatch queue will be executed on the background thread by GCD, and you can’t update UI elements from the background thread. You can’t call show like the above example does.

When you need to update UI elements in the background thread, you need to wrap the code into a block and enqueue it in the "main dispatch queue." You can get the main queue by calling dispatch_get_main_queue(). Then GCD will execute the block on the main thread.

This code shows how to properly update the UI from a background thread.

- (IBAction)upload:(id)sender
{
    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;

        S3PutObjectResponse *response = [s3Client putObject:request];
        if(response.error == nil)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Succeeded!"
                                                                message:@"pubObject succeeded"
                                                               delegate:self
                                                      cancelButtonTitle:@"OK"
                                                      otherButtonTitles:nil];
                [alert show];
            });

            // Do more stuff in the background thread.
        }
    });
}

If you need to do more processing on the background thread after displaying the alert, and you want to make sure that happens after show is called, you can do the following.

- (IBAction)upload:(id)sender
{
    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;

        S3PutObjectResponse *response = [s3Client putObject:request];
        if(response.error == nil)
        {
            dispatch_sync(dispatch_get_main_queue(), ^{
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Succeeded!"
                                                                message:@"pubObject succeeded"
                                                               delegate:self
                                                      cancelButtonTitle:@"OK"
                                                      otherButtonTitles:nil];
                [alert show];
            });

            // Do more stuff in the background thread.
        }
    });
}

dispatch_async returns immediately and the block will be executed at a later time. This is how we can avoid blocking the main thread. dispatch_sync blocks the current thread until the block finishes its execution. It ensures that show has already been executed when you get into the "Do more stuff in the background thread" section. It is similar to sending YES to waitUntilDone: of performSelector:onThread:withObject:waitUntilDone:.

Now you know some basic uses of GCD. You’ll discover that it is a powerful tool, and you can do a lot with it. Once you get more comfortable with GCD, please take a time to read the Concurrency Programming Guide and the Grand Central Dispatch (GCD) Reference in the iOS Developer Library!

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

Further reading: