利用NSURLSession實(shí)現(xiàn)一個(gè)單例管理多文件斷點(diǎn)續(xù)傳

在最近的開發(fā)過程中搁拙,涉及到了一些大文件下載的斷點(diǎn)續(xù)傳問題,這里做一個(gè)歸納和總結(jié)拯腮。首先談?wù)剰木W(wǎng)絡(luò)上下載數(shù)據(jù)有哪里方法:

1.直接用NSData訪問,即

[NSData dataWithContentsOfURL:URL];

這個(gè)方法的缺點(diǎn)很明顯,一個(gè)是會(huì)阻塞當(dāng)前線程怀吻,第二個(gè)只要下載失敗數(shù)據(jù)就不會(huì)得到保存,很顯然不適合斷點(diǎn)續(xù)傳的大文件下載框仔。
2.用NSRULSession(NSURLConnection也是使用代理方法,用法和NSURLSession差不多砾跃,而且已經(jīng)被廢棄骏啰,因此這里不再贅述)的NSURLSessionDownloadTask,即實(shí)現(xiàn)NSURLSessionDownloadDelegate中的以下方法

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    
}

但是這個(gè)方法也有缺點(diǎn):第一 文件下載的位置是放在temp文件夾里蜓席,只要不及時(shí)處理就會(huì)被系統(tǒng)刪除 第二 同時(shí)下載多個(gè)文件時(shí)會(huì)出現(xiàn)錯(cuò)亂器一,及時(shí)自己手動(dòng)把下載好的文件從temp移動(dòng)到cache中也會(huì)出現(xiàn)文件名重復(fù)等各種問題
3.使用AFNetworking試了一下,它也是基于task的一層封裝厨内,具體task還是需要自己手動(dòng)來操作祈秕,也不能實(shí)現(xiàn)斷點(diǎn)續(xù)傳和網(wǎng)絡(luò)請(qǐng)求統(tǒng)一管理,即以下方法

  [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
        
    } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        
    }];
    [manager downloadTaskWithResumeData:resumeData progress:^(NSProgress * _Nonnull downloadProgress) {
        
    } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        
    }];

從這個(gè)方法可以看出雏胃,AFN還是基于NSURLSessionDownloadTask的封裝请毛,并沒有想象中的好用。

這里我做了一個(gè)改進(jìn)瞭亮,即用NSURLSessionDataTask直接發(fā)送get請(qǐng)求來實(shí)現(xiàn)斷點(diǎn)續(xù)傳的下載方仿,而且支持多文件,且看下文分解
  • 既然使用NSURLSession來發(fā)送網(wǎng)絡(luò)請(qǐng)求统翩,這里就在一個(gè)工具類中懶加載一個(gè)session
- (NSURLSession *)session
{
    if (!_session) {
        _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc]init]];
    }
    return _session;
}
  • 有了session還要有task,同樣是懶加載,當(dāng)然這里用的是NSURLDataTask
- (NSURLSessionDataTask *)dataTask
{
    if (!_dataTask) {
        NSError *error = nil;
        NSInteger alreadyDownloadLength = XHRAlreadyDownloadLength;
        //說明已經(jīng)下載完畢
        if ([self.totalDataLengthDictionary[self.fileName]integerValue] && [self.totalDataLengthDictionary[self.fileName] integerValue] == XHRAlreadyDownloadLength)
        {
            !self.completeBlock?:self.completeBlock(XHRFilePath,nil);
            return nil;
        }
        //如果已經(jīng)存在的文件比目標(biāo)大說明下載文件錯(cuò)誤執(zhí)行刪除文件重新下載
        else if ([self.totalDataLengthDictionary[self.fileName] integerValue] < XHRAlreadyDownloadLength)
        {
            [[NSFileManager defaultManager]removeItemAtPath:XHRFilePath error:&error];
            if (!error) {
                alreadyDownloadLength = 0;
            }
            else
            {
                NSLog(@"創(chuàng)建任務(wù)失敗請(qǐng)重新開始");
                return nil;
            }
        }
        //這里是已經(jīng)下載的小于總文件大小執(zhí)行繼續(xù)下載操做

            //創(chuàng)建mutableRequest對(duì)象
            NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.urlString]];
            
            //設(shè)置request的請(qǐng)求頭
            //Range:bytes=xxx-xxx
            [mutableRequest setValue:[NSString stringWithFormat:@"bytes=%ld-",alreadyDownloadLength] forHTTPHeaderField:@"Range"];
            _dataTask = [self.session dataTaskWithRequest:mutableRequest];
        
    }
    return _dataTask;
}

關(guān)于task要說明幾個(gè)判斷的作用:第一仙蚜、判斷是否下載完成,如果下載完成直接調(diào)用下載完成的block厂汗; 第二委粉、判斷本地存儲(chǔ)的文件是否大于目標(biāo)文件,如果大于就說明文件有錯(cuò)誤就得刪除舊文件重新下載娶桦; 第三贾节、就是還未下載完成繼續(xù)下載
關(guān)鍵的幾個(gè)參數(shù):第一、文件的總長(zhǎng)度是第一次接受到服務(wù)器響應(yīng)以后就通過url地址經(jīng)過MD5加密以后形成的唯一的key存儲(chǔ)在字典中并且寫入到沙盒中衷畦;第二栗涂、通過NSFileManager以及文件名獲取當(dāng)前已經(jīng)下載好的文件大小,代碼如下:

[[[NSFileManager defaultManager]attributesOfItemAtPath:XHRFilePath error:nil][NSFileSize] integerValue];

  • 下面來看代理方法中的實(shí)現(xiàn)
    0.初始化的代碼模仿系統(tǒng)祈争,傳入下載進(jìn)度的block和完成是的block,以待適當(dāng)時(shí)候調(diào)用
/**下載方法*/
- (void)downloadFromURL:(NSString *)urlString progress:(void(^)(CGFloat downloadProgress))downloadProgressBlock complement:(void(^)(NSString *filePath,NSError *error))completeBlock;

1.當(dāng)接收到服務(wù)器響應(yīng)以后:

//服務(wù)器響應(yīng)以后調(diào)用的代理方法
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    //接受到服務(wù)器響應(yīng)
    //獲取文件的全部長(zhǎng)度
    self.totalDataLengthDictionary[self.fileName] = @([response.allHeaderFields[@"Content-Length"] integerValue] + XHRAlreadyDownloadLength);
    [self.totalDataLengthDictionary writeToFile:XHRTotalDataLengthDictionaryPath atomically:YES];
    //打開outputStream
    [self.stream open];
    
    //調(diào)用block設(shè)置允許進(jìn)一步訪問
    completionHandler(NSURLSessionResponseAllow);
    
}

2.接收到數(shù)據(jù)以后斤程,一點(diǎn)一點(diǎn)寫入沙盒

//接收到數(shù)據(jù)后調(diào)用的代理方法
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    //把服務(wù)器傳回的數(shù)據(jù)用stream寫入沙盒中
    [self.stream write:data.bytes maxLength:data.length];
    self.downloadProgress = 1.0 * XHRAlreadyDownloadLength / [self.totalDataLengthDictionary[self.fileName] integerValue];
}

3.下載完成后

//任務(wù)完成后調(diào)用的代理方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    if (error) {
        self.downloadError = error;
        return;
    }
    //關(guān)閉流
    [self.stream close];
    //清空task
    [self.session invalidateAndCancel];
    self.dataTask = nil;
    self.session = nil;
    //清空總長(zhǎng)度的字典
    [self.totalDataLengthDictionary removeObjectForKey:self.fileName];
    [self.totalDataLengthDictionary writeToFile:XHRTotalDataLengthDictionaryPath atomically:YES];
    !self.completeBlock?:self.completeBlock(XHRFilePath,nil);
    
}

4.重寫下載進(jìn)度和錯(cuò)誤的setter方法,以監(jiān)聽它們的值,一旦有值直接回調(diào)對(duì)應(yīng)的block

- (void)setDownloadProgress:(CGFloat)downloadProgress
{
    _downloadProgress = downloadProgress;
    !self.downloadProgressBlock?:self.downloadProgressBlock(downloadProgress);
}
- (void)setDownloadError:(NSError *)downloadError
{
    _downloadError = downloadError;
    !self.completeBlock?:self.completeBlock(nil,downloadError);
}
實(shí)現(xiàn)多文件同時(shí)下載的方法

創(chuàng)建一個(gè)downloadManager單例類铛嘱,定義一個(gè)可變字典用來存放對(duì)應(yīng)URL的Session,如果沒有就創(chuàng)建一個(gè)sessionManager對(duì)象來執(zhí)行下載任務(wù)暖释,下載完成后從字典中移除對(duì)應(yīng)的任務(wù)袭厂,檢測(cè)下載到什么程度是在sessionManager完成的downloadManager不需要管墨吓。另外還提供了暫停球匕、繼續(xù)、取消(可以操作單個(gè)帖烘,也可以操作全部)等方法亮曹。
demo以及源碼地址:https://github.com/xuhongru/XHRDownloadManager
作者:胥鴻儒

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市秘症,隨后出現(xiàn)的幾起案子照卦,更是在濱河造成了極大的恐慌,老刑警劉巖乡摹,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件役耕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡聪廉,警方通過查閱死者的電腦和手機(jī)瞬痘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來板熊,“玉大人框全,你說我怎么就攤上這事「汕” “怎么了津辩?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)容劳。 經(jīng)常有香客問我喘沿,道長(zhǎng),這世上最難降的妖魔是什么竭贩? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任蚜印,我火速辦了婚禮,結(jié)果婚禮上娶视,老公的妹妹穿的比我還像新娘晒哄。我一直安慰自己,他們只是感情好肪获,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布寝凌。 她就那樣靜靜地躺著,像睡著了一般孝赫。 火紅的嫁衣襯著肌膚如雪较木。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天青柄,我揣著相機(jī)與錄音伐债,去河邊找鬼预侯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛峰锁,可吹牛的內(nèi)容都是我干的萎馅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼虹蒋,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼糜芳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起魄衅,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤峭竣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后晃虫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皆撩,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年哲银,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扛吞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盘榨,死狀恐怖喻粹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情草巡,我是刑警寧澤守呜,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站山憨,受9級(jí)特大地震影響查乒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜郁竟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一玛迄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棚亩,春花似錦蓖议、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至瘸彤,卻和暖如春修然,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工愕宋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玻靡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓中贝,卻偏偏與公主長(zhǎng)得像囤捻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子雄妥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容