AWS Mobile Blog

Amazon S3 Transfer Utility for iOS

by Yosuke Matsuda | on | Permalink | Comments |  Share

Amazon S3 Transfer Manager for iOS was designed to simplify the data transfer between your iOS app and Amazon S3. It is one of the most used components in our SDK. Two of the most requested features from our developer are 1) the ability to continue transferring data in the background 2) an API to upload binary data instead of having to first save it as a file.

Today we are pleased to announce the introduction of an easier and more powerful way to transfer data between your iOS app and Amazon S3: the Amazon S3 Transfer Utility for iOS (beta). In this blog post, I will show you how to get started with the new Amazon S3 Transfer Utility.

Setup

Amazon Cognito Identity

First, you need to set up Amazon Cognito Identity.

Objective-C

  1. Import the <AWSS3/AWSS3.h> header in your application delegate class.

     #import <AWSS3/AWSS3.h>
    
  2. Set up AWSCognitoCredentialsProvider in the - application:didFinishLaunchingWithOptions: application delegate.

     - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
         AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1
                                                                                                         identityPoolId:@"YourIdentityPoolId"];
         AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1
                                                                              credentialsProvider:credentialsProvider];
         AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = configuration;
    
         return YES;
     }
    

For more information, see the Set Up the SDK for iOS and Amazon Cognito Identity sections of the iOS Developer Guide.

Application Delegate

The Transfer Utility for iOS uses the background transfer feature in iOS that allows data transfers to continue even when your app is not running. Call the following class method in your - application:handleEventsForBackgroundURLSession:completionHandler: application delegate so the OS can tell Transfer Utility when the transfer is complete when the app is not suspended.

Objective-C

- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
    /*
     Store the completion handler.
     */
    [AWSS3TransferUtility interceptApplication:application
           handleEventsForBackgroundURLSession:identifier
                             completionHandler:completionHandler];
}

Upload

Upload a File

Uploading a file is as simple as calling - uploadFile:bucket:key:contentType:expression:completionHander: on AWSS3TransferUtility:

Objective-C

NSURL *fileURL = // The file to upload.

AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
[[transferUtility uploadFile:fileURL
                      bucket:@"YourBucketName"
                         key:@"YourObjectKeyName"
                 contentType:@"text/plain"
                  expression:nil
            completionHander:nil] continueWithBlock:^id(AWSTask *task) {
    if (task.error) {
        NSLog(@"Error: %@", task.error);
    }
    if (task.exception) {
        NSLog(@"Exception: %@", task.exception);
    }
    if (task.result) {
        AWSS3TransferUtilityUploadTask *uploadTask = task.result;
        // Do something with uploadTask.
    }

    return nil;
}];

If you want to know when the upload is complete, you can pass a completion handler block. Also, you can configure the upload behavior by passing AWSS3TransferUtilityUploadExpression. For example, you can add a upload progress feedback block to the expression object. Here is the code snippet with the completion handler and upload progress feedback:

Objective-C

NSURL *fileURL = // The file to upload.

AWSS3TransferUtilityUploadExpression *expression = [AWSS3TransferUtilityUploadExpression new];
expression.uploadProgress = ^(AWSS3TransferUtilityTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Do something e.g. Update a progress bar.
    });
};

AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler = ^(AWSS3TransferUtilityUploadTask *task, NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Do something e.g. Alert a user for transfer completion.
        // On failed uploads, `error` contains the error object.
    });
};

AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
[[transferUtility uploadFile:fileURL
                      bucket:@"YourBucketName"
                         key:@"YourObjectKeyName"
                 contentType:@"text/plain"
                  expression:expression
            completionHander:completionHandler] continueWithBlock:^id(AWSTask *task) {
    if (task.error) {
        NSLog(@"Error: %@", task.error);
    }
    if (task.exception) {
        NSLog(@"Exception: %@", task.exception);
    }
    if (task.result) {
        AWSS3TransferUtilityUploadTask *uploadTask = task.result;
        // Do something with uploadTask.
    }

    return nil;
}];

Upload Binary Data

You can upload an instance of NSData by calling - uploadData:bucket:key:contentType:expression:completionHander:

Objective-C

NSData *dataToUpload = // The data to upload.

AWSS3TransferUtilityUploadExpression *expression = [AWSS3TransferUtilityUploadExpression new];
expression.uploadProgress = ^(AWSS3TransferUtilityTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Do something e.g. Update a progress bar.
    });
};

AWSS3TransferUtilityUploadCompletionHandlerBlock completionHandler = ^(AWSS3TransferUtilityUploadTask *task, NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Do something e.g. Alert a user for transfer completion.
        // On failed uploads, `error` contains the error object.
    });
};

AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
[[transferUtility uploadData:dataToUpload
                      bucket:@"YourBucketName"
                         key:@"YourObjectKeyName"
                 contentType:@"text/plain"
                  expression:expression
            completionHander:completionHandler] continueWithBlock:^id(AWSTask *task) {
    if (task.error) {
        NSLog(@"Error: %@", task.error);
    }
    if (task.exception) {
        NSLog(@"Exception: %@", task.exception);
    }
    if (task.result) {
        AWSS3TransferUtilityUploadTask *uploadTask = task.result;
        // Do something with uploadTask.
    }

    return nil;
}];

This method saves the data as a file in a temporary directory. The next time AWSS3TransferUtility is initialized, the expired temporary files are cleaned up. If you upload many large objects to an Amazon S3 bucket in a short period of time, we recommend that you use the upload file method and manually purge the temporary files as soon as possible for added disk space efficiency.

Download

Here are the code snippets for downloads.

Download to a File

Objective-C

NSURL *fileURL = // The file URL of the download destination.

AWSS3TransferUtilityDownloadExpression *expression = [AWSS3TransferUtilityDownloadExpression new];
expression.downloadProgress = ^(AWSS3TransferUtilityTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Do something e.g. Update a progress bar.
    });
};

AWSS3TransferUtilityDownloadCompletionHandlerBlock completionHandler = ^(AWSS3TransferUtilityDownloadTask *task, NSURL *location, NSData *data, NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Do something e.g. Alert a user for transfer completion.
        // On successful downloads, `location` contains the S3 object file URL.
        // On failed downloads, `error` contains the error object.
    });
};

AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
[[transferUtility downloadToURL:fileURL
                         bucket:S3BucketName
                            key:S3DownloadKeyName
                     expression:expression
               completionHander:completionHandler] continueWithBlock:^id(AWSTask *task) {
    if (task.error) {
        NSLog(@"Error: %@", task.error);
    }
    if (task.exception) {
        NSLog(@"Exception: %@", task.exception);
    }
    if (task.result) {
        AWSS3TransferUtilityDownloadTask *downloadTask = task.result;
        // Do something with downloadTask.
    }

    return nil;
}];

Download as a Binary Data

Objective-C

AWSS3TransferUtilityDownloadExpression *expression = [AWSS3TransferUtilityDownloadExpression new];
expression.downloadProgress = ^(AWSS3TransferUtilityTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Do something e.g. Update a progress bar.
    });
};

AWSS3TransferUtilityDownloadCompletionHandlerBlock completionHandler = ^(AWSS3TransferUtilityDownloadTask *task, NSURL *location, NSData *data, NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // Do something e.g. Alert a user for transfer completion.
        // On successful downloads, `data` contains the S3 object.
        // On failed downloads, `error` contains the error object.
    });
};

AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
[[transferUtility downloadDataFromBucket:S3BucketName
                                     key:S3DownloadKeyName
                              expression:expression
                        completionHander:completionHandler] continueWithBlock:^id(AWSTask *task) {
    if (task.error) {
        NSLog(@"Error: %@", task.error);
    }
    if (task.exception) {
        NSLog(@"Exception: %@", task.exception);
    }
    if (task.result) {
        AWSS3TransferUtilityDownloadTask *downloadTask = task.result;
        // Do something with downloadTask.
    }

    return nil;
}];

Background Transfer

All uploads and downloads continue in the background whether your app is active or in the background. If iOS terminates your app while transfers are ongoing, the system continues the transfers in the background then launches your app after the transfers finish. If the user terminates the app, any ongoing transfers stop.

You can’t persist blocks on disk so you need to rewire the completion handler and progress feedback blocks when your app relaunches. You should call - enumerateToAssignBlocksForUploadTask:downloadTask: on AWSS3TransferUtility to reassign the blocks as needed. Here is an example of reassigning blocks.

Objective-C

- (void)viewDidLoad {
    [super viewDidLoad];

    ...

    AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
    [transferUtility
     enumerateToAssignBlocksForUploadTask:^(AWSS3TransferUtilityUploadTask *uploadTask, __autoreleasing AWSS3TransferUtilityUploadProgressBlock *uploadProgressBlockReference, __autoreleasing AWSS3TransferUtilityUploadCompletionHandlerBlock *completionHandlerReference) {
         NSLog(@"%lu", (unsigned long)uploadTask.taskIdentifier);

         // Use `uploadTask.taskIdentifier` to determine what blocks to assign.

         *uploadProgressBlockReference = // Reassign your progress feedback block.
         *completionHandlerReference = // Reassign your completion handler.
     }
     downloadTask:^(AWSS3TransferUtilityDownloadTask *downloadTask, __autoreleasing AWSS3TransferUtilityDownloadProgressBlock *downloadProgressBlockReference, __autoreleasing AWSS3TransferUtilityDownloadCompletionHandlerBlock *completionHandlerReference) {
         NSLog(@"%lu", (unsigned long)downloadTask.taskIdentifier);

         // Use `downloadTask.taskIdentifier` to determine what blocks to assign.

         *downloadProgressBlockReference =  // Reassign your progress feedback block.
         *completionHandlerReference = // Reassign your completion handler.
     }];
}

You receive AWSS3TransferUtilityUploadTask and AWSS3TransferUtilityDownloadTask when you initiate the upload and download, respectively.

Objective-C

For upload:

if (task.result) {
    AWSS3TransferUtilityUploadTask *uploadTask = task.result;
    // Do something with uploadTask.
}

For download:

if (task.result) {
    AWSS3TransferUtilityDownloadTask *downloadTask = task.result;
    // Do something with downloadTask.
}

There is a property called taskIdentifier that uniquely identifies the transfer task object in the Transfer Utility. You may need to persist the identifier so that you can uniquely identify the upload/download task objects when rewiring the blocks for app relaunch.

Suspend, Resume, and Cancel

To suspend, resume, and cancel uploads and downloads, you need to retain references to AWSS3TransferUtilityUploadTask and AWSS3TransferUtilityDownloadTask. You can simply call - suspend, - resume, and - cancel on them.

Limitations

Behind the scenes, the Transfer Utility generates Amazon S3 pre-signed URLs and uses them to enable background transferring. Using Amazon Cognito Identity, you receive AWS temporary credentials that are valid for up to 60 minutes. It is not possible to generate S3 pre-signed URLs that last longer than 60 minutes. Due to this limitation, the Transfer Utility enforces 50-minute transfer timeouts (10-minute buffer for reducing AWS temporary credential regenerations). After 50 minutes, you receive a transfer failure.

We are exploring ways to overcome the technical limitations. Meanwhile, if you transfer a large amount of data that cannot be transferred within 50 minutes, use AWSS3TransferManager instead.

Talk to Us

We would like to hear from you! Ask questions or share feedback on GitHub issues or the AWS Developer Forum.