likes
comments
collection
share

大文件上传

作者站长头像
站长
· 阅读数 32

前言

简单的demo记录

本文思路

本文实现的思路:

  • 大文件切片.
  • 设置上传的最大并发.
  • 根据YYcache缓存的数据进行断点续传.
  • 设置一个属性, 处理全部上传完成的通知操作.

实现

代码如下:

  • 导入了pod 'AFNetworking', '~> 4.0', YYModel
//

//  ViewController.m

//  大文件上传

//

//  Created by 吉冠坤 on 7/9/2021.

//


#import "ViewController.h"
#import <AFNetworking/AFNetworking.h>
#import "YYCache.h"

@interface ViewController ()

@property (nonatomic, assign) NSInteger pageCount; //切片数量

@end


@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view.
    [self upload];
}

- (void)upload {

    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

    //序列化

    manager.requestSerializer = [AFJSONRequestSerializer serializer];

    manager.responseSerializer = [AFJSONResponseSerializer serializer];

    //请求头设置

    [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    NSString *url = @"url";
    NSDictionary *parameter = @{};
    NSString *filePath = @"文件路径";

    unsigned long long pageSize = 1 << 20;//1兆, 分片大小
    unsigned long long size = fileSizeAtPath(filePath); //文件大小

    int count = ceil(size/pageSize * 1.0); //向上取整

    //在缓存中那数据
    NSMutableDictionary *uploadDic = @{}.mutableCopy;
    YYCache *cache = [[YYCache alloc] initWithName:@"netUpload"];
    if ([cache objectForKey:@"netData"]) {
        //充缓存中拿数据
        uploadDic = (NSMutableDictionary *)[cache objectForKey:@"netData"];
        //设置切片数量
        self.pageCount = uploadDic.allKeys.count;
    } else {
        @autoreleasepool {

            for (int i = 0; i < count; i++) {
                NSString *key = [NSString stringWithFormat:@"%d", i];
                [uploadDic setObject:key forKey:key];
            }
        }
        //设置切片数量
        self.pageCount = count;
        [cache setObject:uploadDic forKey:@"netData"];
    }

    dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);

    dispatch_semaphore_t semp = dispatch_semaphore_create(5);

    dispatch_async(queue, ^{

        NSMutableArray *indexArr = uploadDic.allKeys.mutableCopy;

        for (int i = 0; i < indexArr.count; i++) {

            dispatch_semaphore_wait(semp, DISPATCH_TIME_FOREVER);

            [manager POST:url parameters:parameter headers:nil constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {

                //获取文件偏移

                int index = [indexArr[i] intValue];

                //文件分片

                NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:filePath];

                //根据偏移地址取数据

                //1. 设置偏移地址

                [handle seekToFileOffset:(pageSize * index)];

                //2. 设置读取的大小, 拿到切片数据

                NSData *blockData = [handle readDataOfLength:pageSize];

                //3.上传分片数据

                [formData appendPartWithFileData:blockData name:@"block" fileName:filePath.lastPathComponent mimeType:@"video/mp4"];

            } progress:^(NSProgress * _Nonnull uploadProgress) {

            } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

                //上传成功

                // 1. 处理数据

                [uploadDic removeObjectForKey:indexArr[i]];

                @synchronized (self) {

                    self.pageCount -= 1;

                }

                dispatch_async(dispatch_get_main_queue(), ^{

                    // 2. 主线程更新UI进度

                });

                dispatch_semaphore_signal(semp);

            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

                //上传失败

                // 设置自己的失败机制, 处理失败消息, 看是重新上传还是做其他用户提示操作.

                // 根据缓存  netData 对应的数据, 可以判断传输问题.

                dispatch_semaphore_signal(semp);

            }];

        }

    });

}


- (void)setPageCount:(NSInteger)pageCount {

    _pageCount = pageCount;

    if (pageCount == 0) {

        //处理全部加载完成的 操作

    }

}

long long fileSizeAtPath(NSString* filePath){

    NSFileManager* manager = [NSFileManager defaultManager];

    if ([manager fileExistsAtPath:filePath]){

        return [[manager attributesOfItemAtPath:filePath error:nil] fileSize];

    }

    return 0;

}

@end

总结

如果是小文件还可以转成NSData在内存中操作, 如果是几个G的大文件, 那么只能通过偏移地址利用NSFileHandle一点一点的进行文件切片处理.

其他思路

还有一种思路:

  • 需要上传的数据进行在Document新建一个以文件哈希值为名字的文件夹.

  • 然后利用NSFileHandle对文件进行切片, 在文件夹内对每一个切片新建一个文件, 然后数据存入文件, 命名可以以哈希值命名. 但是在上传的时候需要告诉服务端, 对应的拼接顺序.

  • 在上传的时候, 只需要读文件夹, 拿到数组.

    • 上传文件, 成功之后删除
  • 最后根据文件夹是否为空, 就可以判断是否传输完毕.

这种方式相对来说, 程序中断或者其他的下次启动直接找对应的文件夹, 不需要做其他的处理. 但是解析文件成对应的文件夹然后分片也很耗费I/O. 不太建议, 只是一种思路探讨.

总结

需要注意的问题:

  • 大文件不可以直接加载进内存, 会内存溢出.
    • 通过NSFileHandle类处理
  • 异步上传的时候要告诉服务端两件事:
    • 文件片的拼接方式
    • 文件片传输完成之后的通知.
  • 子线程上传, 主线程刷新UI.
  • 控制并发量.
转载自:https://juejin.cn/post/7005027760104013855
评论
请登录