注意點:
#1,文件的保存路徑不能是網(wǎng)址!?。?#2,NSOutputStream的作用就是保存網(wǎng)路下載的數(shù)據(jù)。
1,NSHTTPURLResponse響應(yīng)對象
1,statusCode:狀態(tài)碼,可以根據(jù)這個值判斷是否請求出錯。
2,allHeaderFields:獲得響應(yīng)體內(nèi)容
3,URL:一般使用在重定向,如果不需要重定向,響應(yīng)的url和請求的url是一樣的。
4,MIMEType:服務(wù)器告訴客戶端返回的數(shù)據(jù)類型
5,textEncodingName :服務(wù)器告訴客戶端返回內(nèi)容的編碼格式
6,expectedContentLength:服務(wù)器返回數(shù)據(jù)的長度,客戶端可以通過該屬性獲得文件大小
7,suggestedFilename:服務(wù)器建議客戶端保存文件使用的名字
2,參考:iOS中流(NSStream)的使用
#NSInputStream 和 NSOutputStream
1,NSInputStream 和 NSOutputStream 是NSStream的兩個子類,分別對應(yīng)了讀文件和 寫文件。
2,NSInputStream 和 NSOutputStream其實是對 CoreFoundation 層對應(yīng)的CFReadStreamRef 和 CFWriteStreamRef 的高層抽象。
3,斷點續(xù)傳
#1,NSURLSessionTask是一個抽象類,本身不能使用,只能使用它的子類
NSURLSessionDataTask((完美斷點)下載)、
NSURLSessionUploadTask(上傳)、
NSURLSessionDownloadTask((有缺陷斷點)下載)
注意:請求的時候,只要有completionHandler結(jié)果回調(diào)的,那么都不會走代理方法。只有下載完成之后才會走completionHandler回調(diào)。
#2,NSURLSessionDownloadTask實現(xiàn)斷點下載(有缺陷)
//如果任務(wù),取消了那么以后就不能恢復(fù)了
// [self.downloadTask cancel];
//如果采取這種方式來取消任務(wù),那么該方法會通過resumeData保存當前文件的下載信息
//只要有了這份信息,以后就可以通過這些信息來恢復(fù)下載
[self.downloadTask cancelByProducingResumeData:^(NSData * __nullable resumeData) {
self.resumeData = resumeData;
}];
-----------
//繼續(xù)下載
//首先通過之前保存的resumeData信息,創(chuàng)建一個下載任務(wù)
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
[self.downloadTask resume];
局限性:
01 如果用戶點擊暫停之后退出程序,那么需要把恢復(fù)下載的數(shù)據(jù)寫一份到沙盒,代碼復(fù)雜度增加
02 如果用戶在下載中途未保存恢復(fù)下載數(shù)據(jù)即退出程序,則不具備可操作性
#3,NSURLSessionDownloadTask兩種請求方法介紹
// 1,該方法不會默認保存存到沙盒tmp文件中(可以通過NSURLSessionDownloadTask并以代理的方式來完成大文件的下載,但是需要手動存到沙盒中)
NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithURL:url];
// 2,內(nèi)部默認已經(jīng)實現(xiàn)了邊下載邊寫入沙盒tmp文件中操作,所以不用開發(fā)人員擔(dān)心內(nèi)存問題
//注意,tmp中存儲的后綴是.tmp,可以通過剪切文件來修改后綴。
// 缺點:不能監(jiān)聽下載的進度。
// NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {}];
4,任務(wù)的三種操作
#啟動task:
[dataTask resume];
#取消task
[dataTask cancel];
#暫停task
[dataTask suspend];
5,實現(xiàn)斷點下載最好的方式是使用NSURLSessionDataTask實現(xiàn)大文件離線斷點下載
#流程:
文件是否下載完成-->文件下載中還是暫停下載-->是否存在文件下載的目錄-->創(chuàng)建流,用NSURLSessionDataTask實現(xiàn)斷點下載(如下)
#注意:一個下載文件URL需要兩部分:task和stream
#HSFileName(url):表示通過URL加密后對應(yīng)的文件名
#通過taskIdentifier獲取task的時候,需要強轉(zhuǎn)一下
//根據(jù)url獲得對應(yīng)的下載任務(wù)
- (NSURLSessionDataTask *)getTask:(NSString *)url
{
return (NSURLSessionDataTask *)[self.tasks valueForKey:HSFileName(url)];
}
#正文:
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
// 創(chuàng)建流
NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:HSFileFullpath(url) append:YES];
// 創(chuàng)建請求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
// 設(shè)置請求頭(從上次下載的長度開始繼續(xù)下載)
NSString *range = [NSString stringWithFormat:@"bytes=%zd-", HSDownloadLength(url)];
[request setValue:range forHTTPHeaderField:@"Range"];
// 創(chuàng)建一個Data任務(wù)(若之前已經(jīng)下載過一部分的文件了,那么這里是從上次下載到的位置開始下載的,這里創(chuàng)建的task就會賦值上新的taskIdentifier,并且把原來URL對應(yīng)的task替換掉。)
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
NSUInteger taskIdentifier = arc4random() % ((arc4random() % 10000 + arc4random() % 10000));
[task setValue:@(taskIdentifier) forKeyPath:@"taskIdentifier"];
// 保存任務(wù)
[self.tasks setValue:task forKey:HSFileName(url)];
HSSessionModel *sessionModel = [[HSSessionModel alloc] init];
sessionModel.url = url;
sessionModel.progressBlock = progressBlock;
sessionModel.stateBlock = stateBlock;
sessionModel.stream = stream;
[self.sessionModels setValue:sessionModel forKey:@(task.taskIdentifier).stringValue];
//開始下載(然后就到代理方法中進行操作即可)
[self start:url];
#pragma mark - 代理
#pragma mark NSURLSessionDataDelegate
/**
* 接收到響應(yīng)(進行打開下載任務(wù)對應(yīng)的流,然后保存該文件的總大小到本地。)
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
HSSessionModel *sessionModel = [self getSessionModel:dataTask.taskIdentifier];
// 打開流
[sessionModel.stream open];
// 獲得服務(wù)器這次請求 返回數(shù)據(jù)的總長度(Content-Length對應(yīng)的是當前下載的URL對應(yīng)的文件大小,也許文件已經(jīng)下載過一部了,現(xiàn)在是繼續(xù)下載的,所以這里后面又加上了已經(jīng)下載的文件的大?。? NSInteger totalLength = [response.allHeaderFields[@"Content-Length"] integerValue] + HSDownloadLength(sessionModel.url);
sessionModel.totalLength = totalLength;
//獲取plist文件 (存儲總長度到plist文件中,HSTotalLengthFullpath:plist文件的路徑)
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:HSTotalLengthFullpath];
if (dict == nil) dict = [NSMutableDictionary dictionary];
dict[HSFileName(sessionModel.url)] = @(totalLength);
[dict writeToFile:HSTotalLengthFullpath atomically:YES];
// 接收這個請求,允許接收服務(wù)器的數(shù)據(jù)。(因為系統(tǒng)默認是不響應(yīng)下載任務(wù)的)
completionHandler(NSURLSessionResponseAllow);
}
/**
* 接收到服務(wù)器返回的數(shù)據(jù)
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
HSSessionModel *sessionModel = [self getSessionModel:dataTask.taskIdentifier];
// 寫入數(shù)據(jù)
[sessionModel.stream write:data.bytes maxLength:data.length];
// 下載進度
NSUInteger receivedSize = HSDownloadLength(sessionModel.url); //已經(jīng)下載的文件的大小
NSUInteger expectedSize = sessionModel.totalLength; //文件的總大小
CGFloat progress = 1.0 * receivedSize / expectedSize;
sessionModel.progressBlock(receivedSize, expectedSize, progress);
}
/**
* 請求完畢(成功|失?。? */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
HSSessionModel *sessionModel = [self getSessionModel:task.taskIdentifier];
if (!sessionModel) return;
if ([self isCompletion:sessionModel.url]) {
// 下載完成
sessionModel.stateBlock(DownloadStateCompleted);
} else if (error){
// 下載失敗
sessionModel.stateBlock(DownloadStateFailed);
}
// 關(guān)閉流
[sessionModel.stream close];
sessionModel.stream = nil;
// 清除任務(wù)
[self.tasks removeObjectForKey:HSFileName(sessionModel.url)];
[self.sessionModels removeObjectForKey:@(task.taskIdentifier).stringValue];
}