关于“分块上传/断点续传“的整理

上传/下载文件过大(如>1G)时,要考虑到网络中断的情况。目前HTTP协议的网络请求本身就已经具备了分片上传能力,传输较大文件比时,协议会自动将文件切片(分块),这种切片是协议内切片方式。在处理大文件时,还可以将大文件主动切片(具体数量自定,分块大小建议相等,并编号),服务器在接收后,再将这些切片按序合并,这就是分片的基本原理。断点续传要求本地要记录每一片的上传状态(如wait、loading、finish),当网络中断后再次连接时,从断点处进行上传。服务器通过文件名、总片数判断该文件是否已全部上传完成。

1. iOS获取文件(音视频、图片)

分两种情况,一种是在相册库里直接获取,一种是调用相机。如果是通过UIImagePickerView来获取,我们会发现当你选定一个视频的时候,会出现图1的压缩页面,最后我们的app获取的视频就是这个经过压缩后的视频(不是视频库里的原始视频,这里有个注意点,操作完该压缩视频后记得释放,系统不会帮你释放的,需要你手动来操作,下面会说到)。

// 然后通过UIImagePickerView的协议方法中的
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info

// 获取视频的fileInfo
UIImagePickerControllerMediaType = "public.movie";
UIImagePickerControllerMediaURL = "file:///private/var/mobile/Containers/Data/Application/2AAE9E44-0E6D-4499-9AC3-93D44D8342EA/tmp/trim.F36EC46C-4219-43C8-96A7-FA7141AB64D2.MOV";
UIImagePickerControllerReferenceURL = "assets-library://asset/asset.MOV?id=DEDA9406-3223-4F87-ABB2-98FB5F5EB9C4&ext=MOV";

UIImagePickerControllerMediaType是选取文件的类型,如KUTTypeImage,KUTTypeMovie(movie和video的区别,一个是有声音的视频文件,一个是没有声音的视频文件,当然还有Audio是只有声音没有视频)。

UIImagePickerControllerMediaURL是视频的URL(如果是相机拍摄的,那么这个就是原始拍摄得到的视频;如果是在相册库里选择的,那就是压缩之后生成的视频),这个URL不指向相册库,通过这个URL你可以操作这个视频如删除,拷贝等,可以获取压缩后的视频的大小。

UIImagePickerControllerReferenceURL是一个指向相册的URL(官方释义,an NSURL that references an asset in the AssetsLibrary framework),通过这个URL,你可以获取视频的所有信息,包括文件名,缩略图,时长等(通过ALAssetsLibrary里的assetsLibraryassetForURL:referenceURLresultBlock:)。

如果是相机拍摄的,注意两个保存方法:

// 高保真压缩图片的方法
NSData *imageData = UIImageJPEGRepresentation(UIImage *image, CGFloat compressionQuality);

// 图片保存到相册
ALAssetsLibrary *assetsLib = [[ALAssetsLibrary alloc] init];
[assetsLib writeImageDataToSavedPhotosAlbum:imageData metadata:nil completionBlock:nil];

// 视频保存到相册:
[assetsLib writeVideoAtPathToSavedPhotosAlbum:MediaURL completionBlock:nil failureBlock:nil];

到这里,我们就获取了所有需要的文件以及文件信息。

2. 将文件切片

由于我们在处理被分片的文件时,涉及用到文件切片的类型、路径、名称、总篇数等信息,因此,我将创建一个文件切片的Model类:


@interface CNFile :NSObject

@property(nonatomic,copy)NSString* fileType;//image or movie
@property(nonatomic,copy)NSString* filePath;//文件在app中路径
@property(nonatomic,copy)NSString* fileName;//文件名
@property(nonatomic,assign)NSIntegerfileSize;//文件大小
@property (nonatomic,assign)NSIntegertrunks;//总片数
@property(nonatomic,copy)NSString* fileInfo;
@property(nonatomic,strong)UIImage* fileImage;//文件缩略图
@property(nonatomic,strong) NSMutableArray* fileArr;//标记每片的上传状态

@end

这样我们就可以对每一个CNFile对象进行操作了。

-(NSData *)readDataWithChunk:(NSInteger)chunk file:(CNFile*)file{
    
    //每一片的大小是1M
    int perSize = 1024*1024;
    
    //总片数的获取方法:
    NSInteger chunks = (file.fileSize%1024 == 0)?((int)(file.fileSize/perSize)):((int)(file.fileSize/perSize + 1));
    NSLog(@"chunks = %ld",(long)chunks);

    // 将文件分片,读取每一片的数据:
    NSData* data;
    NSFileHandle*readHandle = [NSFileHandlefileHandleForReadingAtPath:file.filePath];
    [readHandle seekToFileOffset:perSize * chunk];
    data = [readHandlereadDataOfLength:offset];
    
    return data;
}

3. 文件切片后的上传

上传前,依次询问服务器,该片是否已经存在。如果存在,令chunk+1,重复执行以下方法读取下一片,直到服务器不存在该片,那么上传该片数据。在这个方法中注意设置该chunk的上传状态(wait loading finish),这将关系到本地判断该文件是否已全部上传完成。

- (void)ifHaveData:(NSData*)data WithChunk:(NSInteger)chunk file:(CNFile*)file;

上传的过程:

-(void)uploadData:(NSData*) data WithChunk:(NSInteger) chunk file:(CNFile*)file;

在服务端返回该片上传成功后,我们要做的事有很多:

  1. 先将已经成功上传的本片的flag置finish
[file.fileArr replaceObjectAtIndex:chunk withObject:@"finish"];
  1. 查看是否所有片的flag都已为finish,若都已finish,说明该文件上传完成,那么删除该文件,上传下一个文件或者结束。
for(int j =0; j < chunks; j++) {
  if(j == chunks || ((j == chunks -1)&&([file.fileArr[j]isEqualToString:@"finish"]))) {
    [me deleteFile:file.filePath];
    [me readNextFile];
  }
{
  1. 如果没有都finish,那么看本地下一chunk对用的flag是否是wait
NSLog(@"查看第%ld片的状态",chunk+1);
for(NSInteger i = chunk+1; i < chunks; i++) {
  NSString* flag = [file.fileArrobjectAtIndex:i];
  if([flagisEqualToString:@"wait"]) {
    [mereadDataWithChunk:ifileName:fileNamefile:file];
    break;
  }
}

在第2、3步之间可以有一个 “2.5” ,判断是否暂停上传:

if(me.isPause ==YES) {
  // 将目前读到了第几个文件的第几片保存到本地(注意,对比URLSession中的ResumeData的意义!)
  [self saveProgressWithChunk:chunk file:file];
  return;
}

这个操作实际上和上传过程中断网是一样的,为了断点续传,在断网或者暂停的时候,我们要将目前的进度保存起来,以便下次上传时略过前面已置finish的部分。

显然,我们可以结合多线程使分片上传过程并发执行,同时上传多片,这样就提高了上传效率,并充分利用网络带宽:

dispatch_async(dispatch_queue_t queue, ^{
  [me readDataWithChunk: chunk];
})

最后注意一下,每上传完一个视频,不要忘记释放临时生成的压缩视频,否则,app的空间占用量会剧增。

4. 示例

以下是在网上搜集整理的异步分块上传的实例代码,正确性未知,思路仅供参考:

#pragma mark - 异步上传
- (void)uploadObjectAsync:(NSString *)FileURL objectKey:(NSString *)objectKey{

    OSSPutObjectRequest *put = [OSSPutObjectRequest new];
    NSLog(@"objectKey is %@",objectKey);

    // required fields
    put.bucketName = bucketName;
    put.objectKey = objectKey;
    put.uploadingFileURL = [NSURL fileURLWithPath:FileURL];

    // optional fields
    put.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) {
        NSLog(@"%lld, %lld, %lld", bytesSent, totalByteSent, totalBytesExpectedToSend);

        dispatch_async(dispatch_get_main_queue(), ^{
            float gress = (double)totalByteSent/totalBytesExpectedToSend;
            if([self.dclickDelegate respondsToSelector:@selector(WaveProgressPlus:)]){
                [self.dclickDelegate performSelector:@selector(WaveProgressPlus:) withObject:@(gress)];
                NSLog(@"gress123上传中");
            }
        });
    };

    OSSTask * putTask = [client putObject:put];
    [putTask continueWithBlock:^id(OSSTask *task) {
        NSLog(@" putTask  :%@",putTask.result);
        NSLog(@"objectKey: %@", put.objectKey);
        uploadputkey = put.objectKey;
        if (!task.error) {
            NSLog(@"upload object success!");
            if([self.dclickDelegate respondsToSelector:@selector(dissbackView)]){
                [self.dclickDelegate performSelector:@selector(dissbackView)];
            }
            [self signAccessObjectURL];
        }
        else {
            NSLog(@"upload object failed, error: %@" , task.error);
        }
        return nil;
    }];
}

#pragma mark - 同步上传
- (void)uploadObjectSync:(NSString *)FileURL objectKey:(NSString *)objectKey {
    
    OSSPutObjectRequest * put = [OSSPutObjectRequest new];

    // required fields
    put.bucketName = bucketName;
    put.objectKey = objectKey;
    put.uploadingFileURL = [NSURL fileURLWithPath:FileURL];

    // optional fields
    put.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) {
        NSLog(@"uploadProgress is  %lld, %lld, %lld", bytesSent, totalByteSent, totalBytesExpectedToSend);
        
        dispatch_async(dispatch_get_main_queue(), ^{
            float gress = totalByteSent/totalBytesExpectedToSend;
            if([self.dclickDelegate respondsToSelector:@selector(WaveProgressPlus:)]){
                [self.dclickDelegate performSelector:@selector(WaveProgressPlus:) withObject:@(gress)];
                NSLog(@"gress123");
            }
        });
    };
    OSSTask * putTask = [client putObject:put];
    [putTask waitUntilFinished]; // 阻塞直到上传完成
    NSLog(@" putTask  :%@",putTask.result);

    if (!putTask.error) {
        NSLog(@"upload object success!");
        NSLog(@" putTask  :%@",putTask.result);
        NSLog(@"objectKey: %@", put.objectKey);
        uploadputkey = put.objectKey;

        if([self.dclickDelegate respondsToSelector:@selector(dissbackView)]){
            [self.dclickDelegate performSelector:@selector(dissbackView)];
        }
        [self signAccessObjectURL];
    }
    else {
        NSLog(@"upload object failed, error: %@" , putTask.error);
    }
}

#pragma mark - 追加上传

- (void)appendObject {

    OSSAppendObjectRequest * append = [OSSAppendObjectRequest new];

    // 必填字段
    append.bucketName = @"android-test";
    append.objectKey = @"file1m";
    append.appendPosition = 0; // 指定从何处进行追加
    NSString * docDir = [self getDocumentDirectory];
    append.uploadingFileURL = [NSURL fileURLWithPath:[docDir stringByAppendingPathComponent:@"file1m"]];

    // 可选字段
    append.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) {
        NSLog(@"%lld, %lld, %lld", bytesSent, totalByteSent, totalBytesExpectedToSend);
    };

    // append.contentType = @"";
    // append.contentMd5 = @"";
    // append.contentEncoding = @"";
    // append.contentDisposition = @"";

    OSSTask * appendTask = [client appendObject:append];
    NSLog(@" putTask  :%@",appendTask.result);

    [appendTask continueWithBlock:^id(OSSTask *task) {
        NSLog(@"objectKey: %@", append.objectKey);

        if (!task.error) {
            NSLog(@"append object success!");

            OSSAppendObjectResult * result = task.result;
            NSString * etag = result.eTag;
            long nextPosition = result.xOssNextAppendPosition;
            NSLog(@"etag: %@, nextPosition: %ld", etag, nextPosition);
        }
        else {
            NSLog(@"append object failed, error: %@" , task.error);
        }
        return nil;
    }];
}

#pragma mark - 异步下载
- (void)downloadObjectAsync {

    OSSGetObjectRequest * request = [OSSGetObjectRequest new];

    // required
    request.bucketName = @"android-test";
    request.objectKey = @"file1m";

    //optional
    request.downloadProgress = ^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
        NSLog(@"%lld, %lld, %lld", bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    };

    OSSTask * getTask = [client getObject:request];
    [getTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error) {
            NSLog(@"download object success!");
            OSSGetObjectResult * getResult = task.result;
            NSLog(@"download dota length: %lu", [getResult.downloadedData length]);
        }
        else {
            NSLog(@"download object failed, error: %@" ,task.error);
        }
        return nil;
    }];
}

#pragma mark -  同步下载
- (void)downloadObjectSync {

    OSSGetObjectRequest *request = [OSSGetObjectRequest new];

    // required
    request.bucketName = @"android-test";
    request.objectKey = @"file1m";

    //optional
    request.downloadProgress = ^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
        NSLog(@"%lld, %lld, %lld", bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    };
    
    // NSString * docDir = [self getDocumentDirectory];
    // request.downloadToFileURL = [NSURL fileURLWithPath:[docDir stringByAppendingPathComponent:@"downloadfile"]];
    OSSTask * getTask = [client getObject:request];
    [getTask waitUntilFinished];
    if (!getTask.error) {
        OSSGetObjectResult * result = getTask.result;
        NSLog(@"download data length: %lu", [result.downloadedData length]);
    }
    else {
        NSLog(@"download data error: %@", getTask.error);
    }
}

#pragma mark -  获取meta

- (void)headObject {

    OSSHeadObjectRequest * head = [OSSHeadObjectRequest new];
    head.bucketName = @"android-test";
    head.objectKey = @"file1m";
    OSSTask * headTask = [client headObject:head];
    [headTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error) {
            OSSHeadObjectResult * headResult = task.result;
            NSLog(@"all response header: %@", headResult.httpResponseHeaderFields);
            // some object properties include the 'x-oss-meta-*'s
            NSLog(@"head object result: %@", headResult.objectMeta);
        }
        else {
            NSLog(@"head object error: %@", task.error);
        }
        return nil;
    }];
}

#pragma mark -  删除Object
- (void)deleteObject {

    OSSDeleteObjectRequest * delete = [OSSDeleteObjectRequest new];
    delete.bucketName = @"android-test";
    delete.objectKey = @"file1m";
    OSSTask * deleteTask = [client deleteObject:delete];
    [deleteTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error) {
            NSLog(@"delete success !");
        }
        else {
            NSLog(@"delete erorr, error: %@", task.error);  }
        return nil;
    }];
}

#pragma mark - 复制Object
- (void)copyObjectAsync {

    OSSCopyObjectRequest *copy = [OSSCopyObjectRequest new];
    copy.bucketName = @"android-test"; // 复制到哪个bucket
    copy.objectKey = @"file_copy_to"; // 复制为哪个object
    copy.sourceCopyFrom = [NSString stringWithFormat:@"/%@/%@", @"android-test", @"file1m"]; // 从哪里复制
    OSSTask * copyTask = [client copyObject:copy];

    [copyTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error) {
            NSLog(@"copy success!");
        }
        else {
            NSLog(@"copy error, error: %@", task.error);
        }
        return nil;
    }];
}

#pragma mark - 签名URL授予第三方访问

- (void)signAccessObjectURL {

    NSString * constrainURL = nil;
    NSString * publicURL = nil;

    // sign constrain url
    OSSTask * task = [client presignConstrainURLWithBucketName:bucketName withObjectKey:uploadputkey withExpirationInterval:60 * 30];

//    if (!task.error) {
//        constrainURL = task.result;
//        NSLog(@"constrainURL is %@",constrainURL);
//        postPublicURL = publicURL;
//        [self createTranscodeJobFlow];
//    } else {
//        NSLog(@"error: %@", task.error);
//    }

    // sign public url
    task = [client presignPublicURLWithBucketName:bucketName withObjectKey:uploadputkey];
    if (!task.error) {
        publicURL = task.result;
        NSLog(@"publicURL is %@",publicURL);
        postPublicURL = publicURL;
        [self createTranscodeJobFlow];
    }
    else {
        NSLog(@"sign url error: %@", task.error);
    }
}

#pragma mark   分块上传

- (void)multipartUpload:(NSString *)FileURL objectKey:(NSString *)objectKey{

    __block NSString * uploadId = nil;
    __block NSMutableArray * partInfos = [NSMutableArray new];
    NSString * uploadToBucket = bucketName;
    NSString * uploadObjectkey = objectKey;
    
    OSSInitMultipartUploadRequest * init = [OSSInitMultipartUploadRequest new];
    init.bucketName = uploadToBucket;
    init.objectKey = uploadObjectkey;
    init.contentType = @"video/x-m4v";
    init.objectMeta = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"value1", @"x-oss-meta-name1", nil];

    OSSTask * initTask = [client multipartUploadInit:init];
    [initTask waitUntilFinished];
    if (!initTask.error) {
        OSSInitMultipartUploadResult * result = initTask.result;
        MUuploadID = result.uploadId;
        uploadputkey =objectKey;
        NSLog(@"init multipart upload success: %@", result.uploadId);
    }
    else {
        NSLog(@"multipart upload failed, error: %@", initTask.error);
        return;
    }

    NSDictionary *pathsize =  [self getVideoInfoWithSourcePath:FileURL];
    NSInteger videoSize = [pathsize[@"size"]integerValue];

    //要上传的文件
    chuckCount = 100; // 10块 好区分图谱
    //分片上传数量
    uint64_t offset = videoSize/chuckCount;

    for (int i = 1; i <= chuckCount; i++) {
        @autoreleasepool {
            OSSUploadPartRequest * uploadPart = [OSSUploadPartRequest new];
            uploadPart.bucketName = uploadToBucket;
            uploadPart.objectkey = uploadObjectkey;
            uploadPart.uploadId = uploadId;
            uploadPart.partNumber = i; // part number start from 1
            NSString * docDir = FileURL;
            uploadPart.uploadPartFileURL = [NSURL URLWithString:docDir];
            NSFileHandle* readHandle = [NSFileHandle fileHandleForReadingAtPath:FileURL];
            [readHandle seekToFileOffset:offset * (i -1)];
            NSData* data = [readHandle readDataOfLength:offset];
            uploadPart.uploadPartData = data;

//            uploadPart.uploadPartProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
//                 NSLog(@"%lld %lld %lld", bytesSent, totalBytesSent, totalBytesExpectedToSend);
//                dispatch_async(dispatch_get_main_queue(), ^{
//                    float  gress = (double)totalBytesSent/totalBytesExpectedToSend;
//                    if([self.dclickDelegate respondsToSelector:@selector(WaveProgressPlus:)]){
//                        [self.dclickDelegate performSelector:@selector(WaveProgressPlus:) withObject:@(gress)];
//                        NSLog(@"gress123上传中");
//                    }
//                });
//            };
            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]];
                [self listParts:MUuploadID PartNumber:uploadPart.partNumber];
            }
            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 waitUntilFinished];
//
//    if (!completeTask.error) {
//        NSLog(@"multipart upload success!");
//
//    } else {
//        NSLog(@"multipart upload failed, error: %@", completeTask.error);
//        return;
//    }
}

#pragma mark  罗列分块
- (void)listParts:(NSString *)uploadId PartNumber:(int)partNumber{

    OSSListPartsRequest * listParts = [OSSListPartsRequest new];
    listParts.bucketName = bucketName;
    listParts.objectKey = uploadputkey;
    listParts.uploadId = uploadId;
    
    OSSTask * listPartTask = [client listParts:listParts];
    [listPartTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error) {
            NSLog(@"list part result success!");
            OSSListPartsResult * listPartResult = task.result;
            dispatch_async(dispatch_get_main_queue(), ^{
                float  gress = (double)partNumber/chuckCount;
                if([self.dclickDelegate respondsToSelector:@selector(WaveProgressPlus:)]){
                    [self.dclickDelegate performSelector:@selector(WaveProgressPlus:) withObject:@(gress)];
                    NSLog(@"gress123上传中");
                }
                if (gress == 1.0) {
                    if([self.dclickDelegate respondsToSelector:@selector(dissbackView)]){
                        [self.dclickDelegate performSelector:@selector(dissbackView)];
                    }
                    [self signAccessObjectURL];
                }
            });
            NSLog(@"listPartResult is%@",listPartResult);
            for (NSDictionary * partInfo in listPartResult.parts) {
                NSLog(@"partInfoeachpart: %@", partInfo);
            }
        }
        else {
            NSLog(@"list part result error: %@", task.error);
        }
        return nil;
    }];
}

#pragma mark - 断点续传
- (void)resumableUpload:(NSString *)FileURL objectKey:(NSString *)objectKey{

    __block NSString * recordKey;
//    NSString * docDir = [self getDocumentDirectory];
    NSString * filePath =FileURL;
//    NSString * bucketName = bucketName;
//    objectKey = @"uploadKey";

    [[[[[[OSSTask taskWithResult:nil] continueWithBlock:^id(OSSTask *task) {

        // 为该文件构造一个唯一的记录键
        NSURL * fileURL = [NSURL fileURLWithPath:filePath];
        NSDate * lastModified;
        NSError * error;
        [fileURL getResourceValue:&lastModified forKey:NSURLContentModificationDateKey error:&error];
        if (error) {
            return [OSSTask taskWithError:error];
        }
        recordKey = [NSString stringWithFormat:@"%@-%@-%@-%@", bucketName, objectKey, [OSSUtil getRelativePath:filePath], lastModified];

        // 通过记录键查看本地是否保存有未完成的UploadId
        NSUserDefaults * userDefault = [NSUserDefaults standardUserDefaults];
        return [OSSTask taskWithResult:[userDefault objectForKey:recordKey]];
    }] continueWithSuccessBlock:^id(OSSTask *task) {
        if (!task.result) {
            // 如果本地尚无记录,调用初始化UploadId接口获取
            OSSInitMultipartUploadRequest * initMultipart = [OSSInitMultipartUploadRequest new];
            initMultipart.bucketName = bucketName;
            initMultipart.objectKey = objectKey;
            initMultipart.contentType = @"video/x-m4v";
            return [client multipartUploadInit:initMultipart];
        }
        OSSLogVerbose(@"An resumable task for uploadid: %@", task.result);
        return task;
    }] continueWithSuccessBlock:^id(OSSTask *task) {
        NSString * uploadId = nil;
        if (task.error) {
            return task;
        }
        if ([task.result isKindOfClass:[OSSInitMultipartUploadResult class]]) {
            uploadId = ((OSSInitMultipartUploadResult *)task.result).uploadId;
        }
        else {
            uploadId = task.result;
        }
        if (!uploadId) {
            return [OSSTask taskWithError:[NSError errorWithDomain:OSSClientErrorDomain code:OSSClientErrorCodeNilUploadid userInfo:@{OSSErrorMessageTOKEN: @"Can't get an upload id"}]];
        }

        // 将“记录键:UploadId”持久化到本地存储
        NSUserDefaults * userDefault = [NSUserDefaults standardUserDefaults];
        [userDefault setObject:uploadId forKey:recordKey];
        [userDefault synchronize];
        return [OSSTask taskWithResult:uploadId];
    }] continueWithSuccessBlock:^id(OSSTask *task) {
        // 持有UploadId上传文件
        _resumableUpload = [OSSResumableUploadRequest new];
        _resumableUpload.bucketName = bucketName;
        _resumableUpload.objectKey = objectKey;
        _resumableUpload.uploadId = task.result;
        _resumableUpload.uploadingFileURL = [NSURL fileURLWithPath:filePath];
        _resumableUpload.uploadProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
            NSLog(@"%lld %lld %lld", bytesSent, totalBytesSent, totalBytesExpectedToSend);
            dispatch_async(dispatch_get_main_queue(), ^{
                float  gress = (double)totalBytesSent/totalBytesExpectedToSend;
                if([self.dclickDelegate respondsToSelector:@selector(WaveProgressPlus:)]){
                    [self.dclickDelegate performSelector:@selector(WaveProgressPlus:) withObject:@(gress)];
                    NSLog(@"gress123上传中");
                }
            });
        };
        return [client resumableUpload:_resumableUpload];
    }] continueWithBlock:^id(OSSTask *task) {
        if (task.error) {
            if ([task.error.domain isEqualToString:OSSClientErrorDomain] && task.error.code == OSSClientErrorCodeCannotResumeUpload) {
                // 如果续传失败且无法恢复,需要删除本地记录的UploadId,然后重启任务
                [[NSUserDefaults standardUserDefaults] removeObjectForKey:recordKey];
            }
        } else {
            NSLog(@"%@",task.result);
            NSLog(@"%@",self->_resumableUpload.objectKey);
            NSLog(@"upload completed!");
            uploadputkey = _resumableUpload.objectKey;

            // 上传成功,删除本地保存的UploadId
            [[NSUserDefaults standardUserDefaults] removeObjectForKey:recordKey];
            NSLog(@"upload object success!");
            
            if([self.dclickDelegate respondsToSelector:@selector(dissbackView)]){
                [self.dclickDelegate performSelector:@selector(dissbackView)];
            }
            [self signAccessObjectURL];
        }
        return nil;
    }];
}

-(void)cannelUpdate{

    [_resumableUpload cancel];
//    _resumableUpload = nil;
    NSLog(@"取消网络操作");
//    [OSSTask cancelledTask];
}

#pragma mark - 检查数据大小
- (NSDictionary *)getVideoInfoWithSourcePath:(NSString *)path{

    NSLog(@"%@",path);
    AVURLAsset * asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:path]];
    CMTime time = [asset duration];
    int seconds = ceil(time.value/time.timescale);
    NSInteger fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
    
    return @{@"size" : @(fileSize), @"duration" : @(seconds)};
}

参考文章:
https://mp.weixin.qq.com/s/Ru2L_z0Uc-B_aUuC7_UYvw
https://www.cnblogs.com/tangyuanby2/p/8358925.html
https://blog.csdn.net/weixin_39833509/article/details/88603957
https://blog.csdn.net/qq_30513483/article/details/82733071

你可能感兴趣的:(关于“分块上传/断点续传“的整理)