AWSS3 iOS SDK Tutorial
Create AWSServiceConfiguration
According to the documentation comments, there are two ways to create
Method 1: Customize the creation of ServiceConfiguration
Created according to AK/SK/token
AWSBasicSessionCredentialsProvider *provider = [[AWSBasicSessionCredentialsProvider alloc] initWithAccessKey:access_key secretKey:secret_key sessionToken:token]; AWSRegionType regionType = [AWSEndpoint regionTypeFromName:config.region]; AWSEndpoint *endpoint = [[AWSEndpoint alloc] initWithRegion:regionType service:AWSServiceS3 URL:[NSURL URLWithString:config.endpoint]]; AWSServiceConfiguration *serviceConfig = [[AWSServiceConfiguration alloc] initWithRegion:regionType endpoint:endpoint credentialsProvider:provider];
Method 2: Create based on IdentityPoolId
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1 identityPoolId:@"YourIdentityPoolId"]; AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:credentialsProvider];
Get upload object
Two ways to get AWSS3TransferUtility
Method 1: Get the default service
[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration; AWSS3TransferUtility *S3TransferUtility = [AWSS3TransferUtility defaultS3TransferUtility:^(NSError * _Nullable error) { }]; // or AWSS3TransferUtility *S3TransferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
Method 2:
[AWSS3TransferUtility registerS3TransferUtilityWithConfiguration:configuration forKey:@"USWest2S3TransferUtility"]; // Get registration result [AWSS3TransferUtility registerS3TransferUtilityWithConfiguration:configuration forKey:@"USWest2S3TransferUtility" completionHandler:^(NSError * _Nullable error) { }]; AWSS3TransferUtility *S3TransferUtility = [AWSS3TransferUtility S3TransferUtilityForKey:@"USWest2S3TransferUtility"];
Remove the upload object AWSS3TransferUtility
+ (void)removeS3TransferUtilityForKey:(NSString *)key;
After the upload task is uploaded, remove it.
After listening to AWSS3TransferUtilityURLSessionDidBecomeInvalidNotification, remove it.
Single file upload using AWSS3TransferUtility
Support NSData, NSUrl two ways to upload
// upload progress AWSS3TransferUtilityUploadExpression *expression = [AWSS3TransferUtilityUploadExpression new]; expression.progressBlock = ^(AWSS3TransferUtilityTask *task, NSProgress *progress) { !progressBlock ?: progressBlock(progress); }; // AWSS3TransferUtility object AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility S3TransferUtilityForKey:key]; // Upload NSData [[transferUtility uploadData:data bucket:bucket key:key contentType:contentType expression:expression completionHandler:^(AWSS3TransferUtilityUploadTask * _Nonnull task, NSError * _Nullable error) { // completion callback !completionHandler ?: completionHandler(error); }] continueWithBlock:^id(AWSTask *task) { if (task.error) { NSLog(@"AWSTask.error: %@", task.error); } if (task.result) { NSLog(@"AWSTask.result: %@", task.result); } return nil; }]; // upload URL [[transferUtility uploadFile:fileUrl bucket:bucket key:key contentType:contentType expression:expression completionHandler:^(AWSS3TransferUtilityUploadTask * _Nonnull task, NSError * _Nullable error) { // completion callback !completionHandler ?: completionHandler(error); }] continueWithBlock:^id(AWSTask *task) { if (task.error) { DLog(@"AWSTask.error: %@", task.error); } if (task.result) { DLog(@"AWSTask.result: %@", task.result); } return nil; }];
Use AWSS3TransferUtility to upload large files in parts
Support NSData, NSUrl two ways to upload
// upload progress AWSS3TransferUtilityMultiPartUploadExpression *multipartExpression = [AWSS3TransferUtilityMultiPartUploadExpression new]; multipartExpression.progressBlock = ^(AWSS3TransferUtilityMultiPartUploadTask * _Nonnull task, NSProgress * _Nonnull progress){ !progressBlock ?: progressBlock(progress); }; // upload object AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility S3TransferUtilityForKey:key]; // NSData upload [[transferUtility uploadDataUsingMultiPart:data bucket:bucket key:key contentType:contentType expression:multipartExpression completionHandler:^(AWSS3TransferUtilityMultiPartUploadTask * _Nonnull task, NSError * _Nullable error) { !completionHandler ?: completionHandler(error); }] continueWithBlock:^id _Nullable(AWSTask<AWSS3TransferUtilityMultiPartUploadTask *> * _Nonnull task) { if (task.error) { NSLog(@"AWSTask.error: %@", task.error); } if (task.result) { NSLog(@"AWSTask.result: %@", task.result); } return nil; }]; // NSUrl Upload [[transferUtility uploadFileUsingMultiPart:fileUrl bucket:bucket key:key contentType:contentType expression:multipartExpression completionHandler:^(AWSS3TransferUtilityMultiPartUploadTask * _Nonnull task, NSError * _Nullable error) { !completionHandler ?: completionHandler(error); }] continueWithBlock:^id _Nullable(AWSTask<AWSS3TransferUtilityMultiPartUploadTask *> * _Nonnull task) { if (task.error) { NSLog(@"AWSTask.error: %@", task.error); } if (task.result) { NSLog(@"AWSTask.result: %@", task.result); } return nil; }];
The above method supports: Qiniu/Tencent Cloud, does not support Alibaba Cloud
Use AWSS3
Get AWSS3
[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration; AWSS3 *S3 = [AWSS3 defaultS3]; // or [AWSS3 registerS3WithConfiguration:configuration forKey:@"USWest2S3" AWSS3 *S3 = [AWSS3 S3ForKey:@"USWest2S3"];
Remove AWSS3
+ (void)removeS3ForKey:(NSString *)key;
After all the upload tasks are completed, they can be removed.
Using AWSS3 single file upload
AWSS3PutObjectRequest *request = [[AWSS3PutObjectRequest alloc] init]; request.key = key; request.body = data; request.bucket = bucket; // upload progress request.uploadProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend){ // totalBytes: data.length total length of data // totalBytesSent: length completed NSProgress *progress = [[NSProgress alloc] init]; progress.totalUnitCount = (int64_t)data.length; progress.completedUnitCount = totalBytesSent; !progressBlock ?: progressBlock(progress); }; [S3 putObject:request completionHandler:^(AWSS3PutObjectOutput * _Nullable response, NSError * _Nullable error) { !completionHandler ?: completionHandler(error); }];
This method supports: Qiniu/Tencent Cloud, supports Alibaba Cloud, and needs to change the authentication method
Use AWSS3 to upload large files in parts
The bottom layer of AWSS3TransferUtility large file fragment upload is the same as this method.
System default size per piece: 5 x 1024 x 1024
Multipart upload logic
- Initiate multipart upload event: call the initiateMultipartUpload method to return the globally unique uploadId created by OSS
- Upload part: call uploadPart method to upload part data
- Complete multipart upload: After uploading all parts, call the completeMultipartUpload method to merge all parts into a complete file.
- List the shards being uploaded
- Cancel multipart upload: Call the abortMultipartUpload method to cancel the multipart upload event.
When a part upload event is canceled, this uploadId can no longer be used for any operations, and the uploaded part data will be deleted.
detailed example
// AWSUploadConfig: custom object - (void)AWSS3UploadMultipartData:(NSData *)data config:(AWSUploadConfig *)config progressBlock:(void(^)(NSProgress *progress))progressBlock completionHandler:(void (^)(NSError *error))completionHandler { // get-serviceConfig/register/get-AWSS3 AWSServiceConfiguration *serviceConfig = [self createAWSServiceConfigByUploadConfig:config]; [AWSS3 registerS3WithConfiguration:serviceConfig forKey:config.key]; AWSS3 *S3 = [AWSS3 S3ForKey:config.key]; // 1. Initialize the multipart upload event: Call the [[AWSS3CreateMultipartUploadRequest alloc] init] method to return the globally unique uploadId created by OSS AWSS3CreateMultipartUploadRequest *createRequest = [[AWSS3CreateMultipartUploadRequest alloc] init]; createRequest.bucket = config.bucket; createRequest.key = config.key; WS(weakSelf); [S3 createMultipartUpload:createRequest completionHandler:^(AWSS3CreateMultipartUploadOutput * _Nullable response, NSError * _Nullable error) { if (error) { NSLog(@"createMultipartUpload Creation failed: %@", error); } else { config.uploadId = response.uploadId; NSLog(@"createMultipartUpload created successfully: %@", config.uploadId); [weakSelf AWSS3:S3 uploadPartData:data config:config progressBlock:^(NSProgress *progress) { !progressBlock ?: progressBlock(progress); } complete:^(NSArray *parts) { if (parts.count > 0) { [weakSelf AWSS3:S3 multipartUploadParts:parts config:config complete:^(NSError *error) { !completionHandler ?: completionHandler(error); }]; } }]; } }]; } /// 2. Upload part: call uploadPart method to upload part data - (void)AWSS3:(AWSS3 *)S3 uploadPartData:(NSData *)data config:(AWSUploadConfig *)config progressBlock:(void(^)(NSProgress *progress))progressBlock complete:(void(^)(NSArray *parts))complete { // The default is 5M per piece NSInteger partSize = 5 * 1024 * 1024; NSUInteger chunkSize = ceil((float)data.length /(unsigned long) partSize); __block NSInteger location = 0; __block NSMutableArray<AWSS3CompletedPart *> *parts = [NSMutableArray array]; int64_t totalBytes = data.length; __block int64_t completeBytes = 0; for (int i = 1; location < data.length; i++) { partSize = MIN(partSize, data.length - location); NSData *partData = [data subdataWithRange:NSMakeRange(location, partSize)]; AWSS3UploadPartRequest *uploadRequest = [[AWSS3UploadPartRequest alloc] init]; uploadRequest.bucket = config.bucket; uploadRequest.key = config.key; uploadRequest.uploadId = config.uploadId; uploadRequest.partNumber = @(i); uploadRequest.body = partData; uploadRequest.uploadProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) { completeBytes += bytesSent; NSProgress *progress = [[NSProgress alloc] init]; progress.totalUnitCount = totalBytes; progress.completedUnitCount = completeBytes; !progressBlock ?: progressBlock(progress); }; [S3 uploadPart:uploadRequest completionHandler:^(AWSS3UploadPartOutput * _Nullable response, NSError * _Nullable error) { if (error) { NSLog(@"uploadPart fail: %@", error); } else { AWSS3CompletedPart *part = [[AWSS3CompletedPart alloc] init]; part.ETag = response.ETag; part.partNumber = @(i); [parts addObject:part]; if (parts.count == chunkSize) { // Fragment blocks require ascending order [parts sortUsingComparator:^NSComparisonResult(AWSS3CompletedPart *obj1, AWSS3CompletedPart *obj2) { if ([obj1.partNumber integerValue] < [obj2.partNumber integerValue]) { return NSOrderedAscending; } else { return NSOrderedDescending; } }]; !complete ?: complete(parts); } } }]; location += partSize; } } /// 3. Complete multipart upload: After uploading all parts, call the completeMultipartUpload method to merge all parts into a complete file - (void)AWSS3:(AWSS3 *)S3 multipartUploadParts:(NSArray *)parts config:(AWSUploadConfig *)config complete:(void (^)(NSError *error))complete { AWSS3CompletedMultipartUpload *multipartUpload = [[AWSS3CompletedMultipartUpload alloc] init]; multipartUpload.parts = parts; AWSS3CompleteMultipartUploadRequest *completeRequest = [[AWSS3CompleteMultipartUploadRequest alloc] init]; completeRequest.bucket = config.bucket; completeRequest.key = config.key; completeRequest.uploadId = config.uploadId; completeRequest.multipartUpload = multipartUpload; [S3 completeMultipartUpload:completeRequest completionHandler:^(AWSS3CompleteMultipartUploadOutput * _Nullable response, NSError * _Nullable error) { if (error) { NSLog(@"completeMultipartUpload fail: %@", error); } else { // Remove AWSS3 [S3 removeS3ForKey:config.key]; NSLog(@"completeMultipartUpload success: %@", response.location); !complete ?: complete(error); } }]; } /// 4. List the shards being uploaded - (void)AWSS3:(AWSS3 *)S3 abortMultipartUploadConfig:(AWSUploadConfig *)config { AWSS3ListMultipartUploadsRequest *listRequest = [[AWSS3ListMultipartUploadsRequest alloc] init]; listRequest.bucket = config.bucket; [S3 listMultipartUploads:listRequest completionHandler:^(AWSS3ListMultipartUploadsOutput * _Nullable response, NSError * _Nullable error) { if (error) { NSLog(@"listMultipartUploads upload failed: %@", error); } else { NSInteger count = response.uploads.count; NSLog(@"listMultipartUploads: %ld", count); /// 5. Cancel multipart upload for (AWSS3MultipartUpload *upload in response.uploads) { AWSS3AbortMultipartUploadRequest *abortRequest = [[AWSS3AbortMultipartUploadRequest alloc] init]; abortRequest.bucket = config.bucket; abortRequest.key = config.key; abortRequest.uploadId = upload.uploadId; [S3 abortMultipartUpload:abortRequest completionHandler:^(AWSS3AbortMultipartUploadOutput * _Nullable response, NSError * _Nullable error) { if (error) { NSLog(@"abortMultipartUpload fail: %@", error); } else { NSLog(@"abortMultipartUpload success"); } }]; } } }]; }
This method supports Qiniu and Alibaba Cloud (need to change the authentication method), but does not support Tencent Cloud
download file
/// download file - (void)AWSS3:(AWSS3 *)S3 getObjectWithFilePath:(NSString *)filePath config:(AWSUploadConfig *)config { AWSS3GetObjectRequest *request = [[AWSS3GetObjectRequest alloc] init]; request.key = config.key; request.bucket = config.bucket; request.downloadingFileURL = [NSURL fileURLWithPath:filePath]; [S3 getObject:request completionHandler:^(AWSS3GetObjectOutput * _Nullable response, NSError * _Nullable error) { }]; }
Change AWSS3 signature authentication to support Alibaba Cloud upload
In the AWSSignature.m file,
Change - (NSString *)signS3RequestV4:(NSMutableURLRequest *)urlRequest credentials:(AWSCredentials *)credentials method in the generated authorization
- Change contentSha256, set to contentSha256 = [AWSSignatureSignerUtility hexEncode:[[NSString alloc] initWithData:[AWSSignatureSignerUtility hashData:[urlRequest HTTPBody]] encoding:NSASCIIStringEncoding]];
- Change the setting HTTPBodyStream: [urlRequest setHTTPBodyStream:stream];
details as follows:
- (NSString *)aliyun_signS3RequestV4:(NSMutableURLRequest *)urlRequest credentials:(AWSCredentials *)credentials { if ([urlRequest valueForHTTPHeaderField:@"Content-Type"] == nil) { [urlRequest addValue:@"binary/octet-stream" forHTTPHeaderField:@"Content-Type"]; } NSDate *date = [NSDate aws_clockSkewFixedDate]; NSString *dateStamp = [date aws_stringValue:AWSDateShortDateFormat1]; NSString *scope = [NSString stringWithFormat:@"%@/%@/%@/%@", dateStamp, self.endpoint.regionName, self.endpoint.serviceName, AWSSignatureV4Terminator]; NSString *signingCredentials = [NSString stringWithFormat:@"%@/%@", credentials.accessKey, scope]; // compute canonical request NSString *httpMethod = urlRequest.HTTPMethod; // URL.path returns unescaped path // For S3, url-encoded URI need to be decoded before generate CanonicalURI, otherwise, signature doesn't match occurs. (I.e. CanonicalURI for "/ios-v2-test-445901470/name%3A" will still be "/ios-v2-test-445901470/name%3A". "%3A" -> ":" -> "%3A") NSString *cfPath = (NSString*)CFBridgingRelease(CFURLCopyPath((CFURLRef)urlRequest.URL)); NSString *path = [cfPath aws_stringWithURLEncodingPath]; if (path.length == 0) { path = [NSString stringWithFormat:@"/"]; } NSString *query = urlRequest.URL.query; if (query == nil) { query = [NSString stringWithFormat:@""]; } NSUInteger contentLength = [[urlRequest allHTTPHeaderFields][@"Content-Length"] integerValue]; if (contentLength == 0) { [urlRequest setValue:nil forHTTPHeaderField:@"Content-Length"]; } else { [urlRequest setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[[urlRequest HTTPBody] length]] forHTTPHeaderField:@"Content-Length"]; } // aliyun upload changes // Compute contentSha256 NSString *contentSha256 = [AWSSignatureSignerUtility hexEncode:[[NSString alloc] initWithData:[AWSSignatureSignerUtility hashData:[urlRequest HTTPBody]] encoding:NSASCIIStringEncoding]]; //[request.urlRequest setValue:dateTime forHTTPHeaderField:@"X-Amz-Date"]; [urlRequest setValue:contentSha256 forHTTPHeaderField:@"x-amz-content-sha256"]; //Set Content-MD5 header field if required by server. if (([ urlRequest.HTTPMethod isEqualToString:@"PUT"] && ([[[urlRequest URL] query] hasPrefix:@"tagging"] || [[[urlRequest URL] query] hasPrefix:@"lifecycle"] || [[[urlRequest URL] query] hasPrefix:@"cors"])) || ([urlRequest.HTTPMethod isEqualToString:@"POST"] && [[[urlRequest URL] query] hasPrefix:@"delete"]) ) { if (![urlRequest valueForHTTPHeaderField:@"Content-MD5"]) { [urlRequest setValue:[NSString aws_base64md5FromData:urlRequest.HTTPBody] forHTTPHeaderField:@"Content-MD5"]; } } NSMutableDictionary *headers = [[urlRequest allHTTPHeaderFields] mutableCopy]; NSString *canonicalRequest = [AWSSignatureV4Signer getCanonicalizedRequest:httpMethod path:path query:query headers:headers contentSha256:contentSha256]; AWSDDLogVerbose(@"Canonical request: [%@]", canonicalRequest); NSString *stringToSign = [NSString stringWithFormat:@"%@\n%@\n%@\n%@", AWSSignatureV4Algorithm, [urlRequest valueForHTTPHeaderField:@"X-Amz-Date"], scope, [AWSSignatureSignerUtility hexEncode:[AWSSignatureSignerUtility hashString:canonicalRequest]]]; AWSDDLogVerbose(@"AWS4 String to Sign: [%@]", stringToSign); NSData *kSigning = [AWSSignatureV4Signer getV4DerivedKey:credentials.secretKey date:dateStamp region:self.endpoint.regionName service:self.endpoint.serviceName]; NSData *signature = [AWSSignatureSignerUtility sha256HMacWithData:[stringToSign dataUsingEncoding:NSUTF8StringEncoding] withKey:kSigning]; NSString *signatureString = [AWSSignatureSignerUtility hexEncode:[[NSString alloc] initWithData:signature encoding:NSASCIIStringEncoding]]; NSString *authorization = [NSString stringWithFormat:@"%@ Credential=%@, SignedHeaders=%@, Signature=%@", AWSSignatureV4Algorithm, signingCredentials, [AWSSignatureV4Signer getSignedHeadersString:headers], signatureString]; NSInputStream *stream = [urlRequest HTTPBodyStream]; if (nil != stream) { // Aliyun changes: cancel multi-part upload [urlRequest setHTTPBodyStream:stream]; } return authorization; }