使用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閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • 點擊查看原文 Web SDK 開發手冊 SDK 概述 網易云信 SDK 為 Web 應用提供一個完善的 IM 系統...
    layjoy閱讀 13,854評論 0 15
  • iOS開發系列--網絡開發 概覽 大部分應用程序都或多或少會牽扯到網絡開發,例如說新浪微博、微信等,這些應用本身可...
    lichengjin閱讀 3,705評論 2 7
  • 簡介 用簡單的話來定義tcpdump,就是:dump the traffic on a network,根據使用者...
    保川閱讀 5,981評論 1 13
  • 大約半個月前,我媽在干活的時候小腿骨折了,除了骨折還有幾處裂紋。幾年前她在干農活的時候腳踝裂了,后來打了石膏修養了...
    輕燕舒展閱讀 272評論 0 0