AWS Mobile Blog

S3TransferManager for iOS – Part II: Asynchronous Uploads 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.

This post demonstrates some best practices for achieving the high performance and avoiding common mistakes when using asynchronous uploads with S3TransferManager.

Delegate methods may receive different subclasses of AmazonServiceRequest and AmazonServiceResponse

Because S3TransferManager uses multipart uploads for large data and put object requests for small data, the delegate methods return different subclasses of AmazonServiceRequest and AmazonServiceResponse for each case. For example:

  • request:didSendData:totalBytesWritten:totalBytesExpectedToWrite: returns

    • S3PutObjectRequest for small data
    • S3UploadPartRequest for large data
  • request:didCompleteWithResponse: returns

    • S3PutObjectResponse for small data
    • S3CompleteMultipartUploadResponse for large data.

When you need to retrieve properties from the subclass, do not indiscriminately cast the AmazonServiceRequest or AmazonServiceResponse object to the subclass.

-(void)request:(AmazonServiceRequest *)request didSendData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    // Do not do this!
    S3UploadPartRequest *uploadPartReqeust = (S3UploadPartRequest *)request;
    NSInteger partNumber = uploadPartReqeust.partNumber;
    NSLog(@"Do something with %d", partNumber);
}

Instead, call respondsToSelector: first to make sure that the request or response can respond to the selector, and then call performSelector::

-(void)request:(AmazonServiceRequest *)request didSendData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    if([request respondsToSelector:@selector(partNumber)])
    {
        NSInteger partNumber = [request performSelector:@selector(partNumber)];
        NSLog(@"Do something with %d", partNumber);
    }
}

Alternatively, you can use isKindOfClass: or isMemberOfClass: as follows:

-(void)request:(AmazonServiceRequest *)request didSendData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    if([request isKindOfClass:[S3UploadPartRequest class]])
    {
        S3UploadPartRequest *uploadPartReqeust = (S3UploadPartRequest *)request;
        NSInteger partNumber = uploadPartReqeust.partNumber;
        NSLog(@"Do something with %d", partNumber);
    }
}

Use requestTag to uniquely identify a request

Although it is possible to uniquely identify a request with bucket and key names in many situations, it doesn’t always guarantee the uniqueness of a request. As mentioned in the previous post, it is often beneficial to use requestTag to uniquely identify request.

putObjectRequest.requestTag = @"your-unique-tag";
[self.tm upload:putObjectRequest];

The requestTag property is a free format NSString, so you can pick any naming strategies that make sense for your apps.

-(void)request:(AmazonServiceRequest *)request didSendData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    if([request.requestTag isEqualToString:@"your-unique-tag"])
    {
        // Update the progress bar for "your-unique-tag".
    }
}

Each thread should have its own instance of S3TransferManager

We recommend that you create an S3TransferManager object for each thread, and do not share that object among multiple threads. It makes your code simpler by eliminating lots of thread controlling logic.

The operationQueue property of S3TransferManager is static, and is shared among all instances of S3TransferManager objects. This ensures that the maxConcurrentOperationCount is imposed among all instances of S3TransferManager. When updating any properties of operationQueue or calling any methods on it, please keep in mind that other threads may be affected by the changes even though NSOperationQueue itself is thread-safe.

Delegate of S3TransferManager will be shared

Whenever you set the delegate to S3TransferManager and do not set the delegate of S3PutObjectRequest, the S3TransferManager‘s delegate will be used. It is possible that multiple requests share the same delegate.

Avoid resetting the delegate property of S3TransferManager as much as possible once set because it may affect other requests running on the same S3TransferManager.

Though it’s not encouraged, when you have to use S3TransferManager from multiple classes or threads, consider using the delegate property of S3PubObjectRequest because it is unique to each request, and has minimal side effects on other requests.

Do not block the main thread

The AmazonServiceRequestDelegate methods are always called on the main thread. It is safe to update UIs on the delegate methods, but never call blocking methods on them. Take a look at this post for discussion on why you should never block the main thread.

Best practices for using AmazonServiceRequestDelegate apply

Because asynchronous S3TransferManager operations use AmazonServiceRequestDelegate, these best practices apply.

Please let us know if you have any questions.

Further reading

We are hiring

If you like building mobile applications that use cloud services that our customers use on a daily basis, perhaps you would like to join the AWS Mobile SDK and Tools team. We are hiring Software Developers, Web Developers, and Product Managers.