在最近的開發(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
作者:胥鴻儒