iOS分片上传

OSS的分片上传(Multipart Upload)用于将要上传的较大的文件(Object)分成多个数据块(OSS中称之为Part)来分别上传,上传完成后再调用CompleteMultipartUpload接口将这些Part组合成一个Object。

注意事项

  • 使用本文示例前您需要先通过自定义域名、STS等方式新建OSSClient,具体请参见初始化

    说明

    所创建存储空间的所属地域取决于初始化配置的endpoint地域信息。

  • 要上传文件,您必须有oss:PutObject权限。具体操作,请参见RAM用户授权自定义的权限策略

分片上传流程

分片上传的基本流程如下:

  1. 将要上传的文件按照一定的大小分片。

  2. 初始化一个分片上传任务(InitiateMultipartUpload)。

  3. 逐个或并行上传分片(UploadPart)。

  4. 完成上传(CompleteMultipartUpload)。

image

该过程需注意以下几点:

  • 除了最后一块Part,其他Part的大小不能小于100 KB,否则会导致调用CompleteMultipartUpload接口失败。

  • 要上传的文件切分成Part之后,文件顺序是通过上传过程中指定的partNumber来确定的,实际执行中并没有顺序要求,因此可以实现并发上传。

    具体的并发个数并不是越多速度越快,要结合用户自身的网络情况和设备负载综合考虑。网络情况较好时,建议增大分片大小。反之,减小分片大小。

  • 默认情况下,已经上传但还没有调用CompleteMultipartUploadPart是不会被自动回收的,因此如果要终止上传并删除占用的空间请调用AbortMultipartUpload。如果需要自动回收上传的Part,请参见生命周期管理

分片上传完整示例

以下通过一个完整的示例对分片上传的流程进行逐步解析:

__block NSString * uploadId = nil;
__block NSMutableArray * partInfos = [NSMutableArray new];
// 填写Bucket名称,例如examplebucket。
NSString * uploadToBucket = @"examplebucket";
// 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
NSString * uploadObjectkey = @"exampledir/exampleobject.txt";
// OSSInitMultipartUploadRequest用于指定上传文件的名称以及上传文件所属的存储空间的名称。
OSSInitMultipartUploadRequest * init = [OSSInitMultipartUploadRequest new];
init.bucketName = uploadToBucket;
init.objectKey = uploadObjectkey;
// init.contentType = @"application/octet-stream";
// multipartUploadInit返回的结果中包含UploadId,UploadId是分片上传的唯一标识。
OSSTask * initTask = [client multipartUploadInit:init];
[initTask waitUntilFinished];
if (!initTask.error) {
    OSSInitMultipartUploadResult * result = initTask.result;
    uploadId = result.uploadId;
    // 根据uploadId执行取消分片上传事件或者列举已上传分片的操作。
    // 如果您需要根据您需要uploadId执行取消分片上传事件的操作,您需要在调用InitiateMultipartUpload完成初始化分片之后获取uploadId。
    // 如果您需要根据您需要uploadId执行列举已上传分片的操作,您需要在调用InitiateMultipartUpload完成初始化分片之后,且在调用CompleteMultipartUpload完成分片上传之前获取uploadId。
    //NSLog(@"UploadId": %@, uploadId);
} else {
    NSLog(@"multipart upload failed, error: %@", initTask.error);
    return;
}

// 指定要上传的文件。
NSString * filePath = @"<filepath>";
// 获取文件大小。
uint64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil] fileSize];
// 设置分片上传数量。
int chuckCount = 10;
// 设置分片大小。
uint64_t offset = fileSize/chuckCount;
for (int i = 1; i <= chuckCount; i++) {
    OSSUploadPartRequest * uploadPart = [OSSUploadPartRequest new];
    uploadPart.bucketName = uploadToBucket;
    uploadPart.objectkey = uploadObjectkey;
    uploadPart.uploadId = uploadId;
    uploadPart.partNumber = i; // part number start from 1

    NSFileHandle* readHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
    [readHandle seekToFileOffset:offset * (i -1)];

    NSData* data = [readHandle readDataOfLength:offset];
    uploadPart.uploadPartData = data;

    OSSTask * uploadPartTask = [client uploadPart:uploadPart];

    [uploadPartTask waitUntilFinished];

    if (!uploadPartTask.error) {
        OSSUploadPartResult * result = uploadPartTask.result;
        uint64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:uploadPart.uploadPartFileURL.absoluteString error:nil] fileSize];
        [partInfos addObject:[OSSPartInfo partInfoWithPartNum:i eTag:result.eTag size:fileSize]];
    } else {
        NSLog(@"upload part error: %@", uploadPartTask.error);
        return;
    }
}
OSSCompleteMultipartUploadRequest * complete = [OSSCompleteMultipartUploadRequest new];
complete.bucketName = uploadToBucket;
complete.objectKey = uploadObjectkey;
complete.uploadId = uploadId;
complete.partInfos = partInfos;

OSSTask * completeTask = [client completeMultipartUpload:complete];

[[completeTask continueWithBlock:^id(OSSTask *task) {
    if (!task.error) {
        OSSCompleteMultipartUploadResult * result = task.result;
        // ...
    } else {
        // ...
    }
    return nil;
}] waitUntilFinished];

本地文件分片上传

说明

分片上传完整示例是按照分片上传流程逐步实现的完整代码,本地文件分片上传的代码是将分片上传完整示例中的代码进行了封装,您只需要使用MultipartUploadRequest即可实现分片上传。

以分片上传的方式上传本地文件的代码示例如下。

// 填写Bucket名称,例如examplebucket。
NSString *bucketName = @"examplebucket";
// 填写文件完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
NSString *objectKey = @"exampledir/exampleobject.txt";

OSSMultipartUploadRequest * multipartUploadRequest = [OSSMultipartUploadRequest new];
multipartUploadRequest.bucketName = bucketName;
multipartUploadRequest.objectKey = objectKey;
// 设置分片大小,默认256 KB。
multipartUploadRequest.partSize = 1024 * 1024;
multipartUploadRequest.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) {
    NSLog(@"progress: %lld, %lld, %lld", bytesSent, totalByteSent, totalBytesExpectedToSend);
};

multipartUploadRequest.uploadingFileURL = [[NSBundle mainBundle] URLForResource:@"wangwang" withExtension:@"zip"];
OSSTask * multipartTask = [client multipartUpload:multipartUploadRequest];
[[multipartTask continueWithBlock:^id(OSSTask *task) {
    if (task.error) {
        NSLog(@"error: %@", task.error);
    } else {
        NSLog(@"Upload file success");
    }
    return nil;
}] waitUntilFinished];

列举已上传分片

调用listParts方法获取某个上传事件所有已上传的分片。

OSSListPartsRequest * listParts = [OSSListPartsRequest new];
// 填写Bucket名称,例如examplebucket。
listParts.bucketName = @"examplebucket";
// 填写文件完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
listParts.objectKey = @"exampledir/exampleobject.txt";
// 填写uploadId。uploadId来源于调用InitiateMultipartUpload完成初始化分片之后,且在调用CompleteMultipartUpload完成分片上传之前的返回结果。
listParts.uploadId = @"0004B999EF518A1FE585B0C9****";

OSSTask * listPartTask = [client listParts:listParts];

[listPartTask continueWithBlock:^id(OSSTask *task) {
    if (!task.error) {
        NSLog(@"list part result success!");
        OSSListPartsResult * listPartResult = task.result;
        for (NSDictionary * partInfo in listPartResult.parts) {
            NSLog(@"each part: %@", partInfo);
        }
    } else {
        NSLog(@"list part result error: %@", task.error);
    }
    return nil;
}];
// waitUntilFinished会阻塞当前线程,但是不会阻塞上传任务进程。
// [listPartTask waitUntilFinished];            
重要

默认情况下,如果存储空间中的分片上传事件的数量大于1000,则OSS仅返回1000Multipart Upload信息,且返回结果中IsTruncated的值为false,并返回NextPartNumberMarker作为下次读取的起点。

取消分片上传

以下代码用于取消了对应UploadId的分片上传请求。

OSSAbortMultipartUploadRequest * abort = [OSSAbortMultipartUploadRequest new];
// 填写Bucket名称,例如examplebucket。
abort.bucketName = @"examplebucket";
// 填写文件完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
abort.objectKey = @"exampledir/exampleobject.txt";
// 填写uploadId。uploadId来源于调用InitiateMultipartUpload完成初始化分片之后的返回结果。
abort.uploadId = @"0004B999EF518A1FE585B0C9****";

OSSTask * abortTask = [client abortMultipartUpload:abort];

[abortTask waitUntilFinished];

if (!abortTask.error) {
    OSSAbortMultipartUploadResult * result = abortTask.result;
    uploadId = result.uploadId;
} else {
    NSLog(@"multipart upload failed, error: %@", abortTask.error);
    return;
}
// waitUntilFinished会阻塞当前线程,但是不会阻塞上传任务进程。
// [abortTask waitUntilFinished];            

相关文档

  • 关于分片上传的完整示例代码,请参见GitHub示例

  • 分片上传的完整实现涉及三个API接口,详情如下:

  • 关于列举已上传分片的API接口说明,请参见ListParts

  • 关于取消分片上传事件的API接口说明,请参见AbortMultipartUpload