Front-End Web & Mobile
Amazon S3 Transfer Utility for iOS
As a result of recent updates to the AWS Mobile SDK for iOS, please refer to the AWS Mobile SDK for iOS Developer Guide for the latest guidance on how to upload and download files from Amazon S3 on iOS. Refer to the iOS Sample Repository for code samples.
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
- Import the
<AWSS3/AWSS3.h>
header in your application delegate class.#import <AWSS3/AWSS3.h>
- 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.