使用NSURLSession实现iOS大文件分块下载

如果在网速一定的情境下,大文件(目前指的是100M以上的文件)的下载对用户来说是一段不短的时间,用户体验不是很好。
分块下载文件实现原理
如果要实现文件的分段下载,我们首先需要知道要下载的文件的大小,这里需要向服务器发送HTTP请求,请求方法为HEAD,这样服务器只会给客户端返回response的包头信息,不会发送数据信息,然后我们通过包头信息中的Content-Length字段可以得到要下载的文件的总长度。

- (void)getFileTotalLengthWithURL:(NSString *)url
                       completion:(void(^)(NSInteger length))completion{
    NSURL *URL = [NSURL URLWithString:url];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
    request.HTTPMethod = @"HEAD";
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSHTTPURLResponse *tmpResponse = (NSHTTPURLResponse *)response;
            NSLog(@"allHeaderFields:%@", tmpResponse.allHeaderFields);
        }
        NSInteger fileTotalLength = response.expectedContentLength;
        completion(fileTotalLength);
    }];
    [dataTask resume];
}

获取到要下载的文件的总长度之后,在本地沙盒中创建一个同样大小的文件

- (void)multiDownloadWithFileLength:(NSInteger)fileLength url:(NSURL *)url{
    _wholeFileLength = fileLength;

    NSString *filePath = [self filePathWithFileName:url.lastPathComponent];
    NSFileManager *fm = [NSFileManager defaultManager];
    if ([fm fileExistsAtPath:filePath]) {
        [fm removeItemAtPath:filePath error:nil];
    }
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];

    _filePath = filePath;
    _fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
    [_fileHandle truncateFileAtOffset:fileLength];
    
    NSBlockOperation *addOperationOP = [NSBlockOperation blockOperationWithBlock:^{
        while (_completedLength < fileLength) {
            long long startSize = _completedLength;
            long long endSize = startSize+blockSize;
            
            if (endSize > fileLength) {
                endSize = fileLength - 1;
                _completedLength = fileLength;
            } else {
                _completedLength += blockSize;
            }
            
            //一个operation对应一个downloadTask
            
            NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
                
                NSString *range=[NSString stringWithFormat:@"bytes=%lld-%lld", startSize, endSize];
                NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
                [request setValue:range forHTTPHeaderField:@"Range"];
                NSLog(@"requestHeader:%@", request.allHTTPHeaderFields);
                NSURLSessionDownloadTask *task = [self.session downloadTaskWithRequest:request];
                
                [task resume];
                
            }];
            [_queue addOperation:operation];
        }
    }];
    [_queue addOperation:addOperationOP];
    
}

每个请求块的大小

#define blockSize 1024*1024

新建一个下载队列,循环发送请求,在请求头中设置Range字段

NSOperationQueue *queue = [NSOperationQueue currentQueue];
    NSBlockOperation *addOperationOP = [NSBlockOperation blockOperationWithBlock:^{
        while (_completedLength < fileLength) {
            long long startSize = _completedLength;
            long long endSize = startSize+blockSize;
            
            if (endSize > fileLength) {
                endSize = fileLength - 1;
                _completedLength = fileLength;
            } else {
                _completedLength += blockSize;
            }
            
            //一个operation对应一个downloadTask
            
            NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
                
                NSString *range=[NSString stringWithFormat:@"bytes=%lld-%lld", startSize, endSize];
                NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
                [request setValue:range forHTTPHeaderField:@"Range"];
                NSLog(@"requestHeader:%@", request.allHTTPHeaderFields);
                NSURLSessionDownloadTask *task = [self.session downloadTaskWithRequest:request];
                
                [task resume];
                
            }];
            [queue addOperation:operation];
        }
    }];
    [queue addOperation:addOperationOP];

在代理方法中将获取到的数据写到已经创建好的空文件的对应的位置中

 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
    
    DLQData *tmpReceivedData = [[DLQData alloc] init];
    NSInteger startSize = 0;
    NSInteger endSize = 0;
    
    if ([downloadTask.response isKindOfClass:[NSHTTPURLResponse class]]) {
        NSHTTPURLResponse *tmpResponse = (NSHTTPURLResponse *)downloadTask.response;
        NSDictionary *dic = tmpResponse.allHeaderFields;
        NSLog(@"diiiiiic: %@", dic[@"Content-Range"]);
        NSString *fileRange = dic[@"Content-Range"];
        fileRange = [fileRange stringByReplacingOccurrencesOfString:@"bytes" withString:@""];
        fileRange = [fileRange stringByReplacingOccurrencesOfString:@" " withString:@""];
        NSArray *aTmp1 = [fileRange componentsSeparatedByString:@"/"];
        NSArray *aTmp2 = @[];
        if (aTmp1.count) {
            NSString *tmpStr = aTmp1[0];
            aTmp2 = [tmpStr componentsSeparatedByString:@"-"];
            if (aTmp1.count >= 2) {
                NSString *startSizeStr = aTmp2[0];
                NSString *endSizeStr = aTmp2[1];
                startSize = startSizeStr.integerValue;
                endSize = endSizeStr.integerValue;
        
                tmpReceivedData.data = [NSData dataWithContentsOfURL:location];
                tmpReceivedData.startSize = startSize;
                tmpReceivedData.endSize = endSize;
                
                [_fileHandle seekToFileOffset:tmpReceivedData.startSize];
                [_fileHandle writeData:tmpReceivedData.data];
                [_fileData appendData:tmpReceivedData.data];
                
                double progress = _fileData.length/_wholeFileLength;
                progress = progress >= 1 ? 1 : progress;
                if (progress == 1) {
                    NSLog(@"分段下载完成");
                    NSLog(@"downloadProgress:%f", progress);
                    
                    [_operationQueue cancelAllOperations];
                    _operationQueue = nil;
                    
                    if ([self.delegate respondsToSelector:@selector(multiDownloadDidFinished:)] && [self.delegate respondsToSelector:@selector(multiDownloadProgress:)]) {
                        NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
                        [mainQueue addOperationWithBlock:^{
                            [self.delegate multiDownloadProgress:progress];
                            [self.delegate multiDownloadDidFinished:_filePath];

                        }];
                    }
                }else{
                    if ([self.delegate respondsToSelector:@selector(multiDownloadProgress:)]) {
                        NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
                        [mainQueue addOperationWithBlock:^{
                            [self.delegate multiDownloadProgress:progress];
                        }];
                    }
                }
            }
        }
    }
}

点我看Demo

参考文章
iOS开发网络篇—多线程断点下载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,662评论 0 15
  • iOS开发系列--网络开发 概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博、微信等,这些应用本身可...
    lichengjin阅读 3,637评论 2 7
  • 简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者...
    保川阅读 5,941评论 1 13
  • 大约半个月前,我妈在干活的时候小腿骨折了,除了骨折还有几处裂纹。几年前她在干农活的时候脚踝裂了,后来打了石膏修养了...
    轻燕舒展阅读 253评论 0 0