Apple 在 iOS 7 之後將原有的 NSURLConnection 替換成了新的 NSURLSession ,新的 NSURLSession 包含諸多新的特性,其中有一項就是在應用退出到後臺以及崩潰後,系統可以繼續幫我們進行上傳、下載任務。同時,NSURLSession 對一些任務進行了很好的封裝。例如我們只需要使用下面這種方法就可以開始一個下載任務,非常簡單。
task = [self.session downloadTaskWithResumeData:resumeData];
同時, NSURLSession 對進行中的任務也可以做到很好的管理。通過系統的 delegate 方法,例如
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
//可以獲取進度等
}
複製程式碼
#####NSURLSessionDownloadTask斷點續傳
NSURLSessionDownloadTask 可以通過 suspend
方法進行暫停,並在程式沒有被終止之前通過 resume
方法進行恢復。也可以通過 [task cancelByProducingResumeData:^(NSData *resumeData){ }];
進行暫停,同樣也是通過resume
恢復下載。
[task cancelByProducingResumeData:^(NSData *resumeData){
//localPath:本地路徑
[resumeData writeToFile:localPath atomically:YES];
}];
複製程式碼
這樣就可以通過這個 resumeData 就可以方便的在應用關閉、重新啟動後進行下載任務的續傳了。 ####實際運用中碰到的問題 專案中使用 NSURLSession 來完成一些下載任務。需要續傳時很自然的就使用到了 resumeData 來恢復下載任務。想象很美好,現實很殘酷。因為專案中的下載任務,資源的 URL 是有時效的,每一次請求資源的 URL 都會發生改變。當從 resumeData 中恢復出一個 NSURLSessionDownloadTask 後,由於在 resumeData 中包含了第一次請求的 request。所以開始任務後就會出錯。收到的響應狀態碼是 404,因為資源的 URL 發生了改變。 ####resumeData的內容 將 resumeData 存入一個檔案後開啟,格式是這樣的:
<?xml version="1.0" encoding="UTF-8"?\>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"\>
<plist version="1.0">
<dict>
<key>NSURLSessionDownloadURL</key>
<string>https://d1opms6zj7jotq.cloudfront.net/idea/ideaIC-15.0.2-custom-jdk-bundled.dmg</string>
<key>NSURLSessionResumeBytesReceived</key>
<integer>25858975</integer>
<key>NSURLSessionResumeCurrentRequest</key>
<data>
YnBsaXN0MDDUAQIDBAUGcnNYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
AAGGoK8QFwcIKUVLTFJTVFU4VjpXWGRlZmdoaWpuVSRudWxs3xAeCQoLDA0ODxAREhMU
FRYXGBkaGxwdHh8gISIjJCUmJygpKissLSgvMDErMysqKio4Jzo7Kj0qPyo7QjhDUyQx
... 後面省略
</data>
<key>NSURLSessionResumeEntityTag</key>
<string>"054beecc2c7dbd16d58ecb9084ab4b79-19"</string>
<key>NSURLSessionResumeInfoTempFileName</key>
<string>CFNetworkDownload_desnj1.tmp</string>
<key>NSURLSessionResumeInfoVersion</key>
<integer>2</integer>
<key>NSURLSessionResumeOriginalRequest</key>
<data>
YnBsaXN0MDDUAQIDBAUGTk9YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
AAGGoKwHCCM5P0BGR0gvSUpVJG51bGzfEBgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAh
IiMkJSInKCOg==
... 後面省略
</data>
<key>NSURLSessionResumeServerDownloadDate</key>
<string>Mon, 07 Dec 2015 21:52:40 GMT</string>
</dict>
</plist>
複製程式碼
其中NSURLSessionDownloadURL是資源的URL。NSURLSessionResumeBytesReceived記錄下來了已經下載完成的位元組數。NSURLSessionResumeCurrentRequest以及NSURLSessionResumeOriginalRequest是當前以及初始請求時的物件。是通過NSKeyArchiverencode出的資料。NSURLSessionResumeEntityTag是 E-Tag部分。NSURLSessionResumeInfoTempFileNam是下載過程中臨時檔案所儲存的位置,儲存在應用程式tmp 資料夾下。我們需要的就是這幾個部分。 ####重新生成 ResumeData 上程式碼:
- (NSData *)newResumeDataFromOldResumeData:(NSData *)resumeData withURLString:(NSString *)urlString{
if (!resumeData || !urlString ) return nil;
NSMutableDictionary *resumeDataDic =[NSPropertyListSerialization propertyListWithData:resumeData options:NSPropertyListImmutable format:nil error:nil];
//生成一個新的NSURLMutableRequest
NSMutableURLRequest *newResumeRequest =[NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
//為NSURLMutableRequest 為 HTTP 請求的頭部增加了一個 Range
NSInteger bytes =[[resumeDataDic objectForKey:@"NSURLSessionResumeBytesReceived"] integerValue];
NSString *bytesStr =[NSString stringWithFormat:@"bytes=%ld-",bytes];
[newResumeRequest addValue:bytesStr forHTTPHeaderField:@"Range"];
// 重新將 NSURLMutableRequest encode 為 NSData 。
NSData *newResumeData =[NSKeyedArchiver archivedDataWithRootObject:newResumeRequest];
[resumeDataDic setObject:newResumeData forKey:@"NSURLSessionResumeCurrentRequest"];
[resumeDataDic setObject:urlString forKey:@"NSURLSessionDownloadURL"];
NSData *data =[NSPropertyListSerialization dataWithPropertyList:resumeDataDic format:NSPropertyListXMLFormat_v1_0 options:0 error:nil];
return data;
}
複製程式碼
將 plist 檔案重新讀迴應用中後是一個 NSData 檔案,首先第一步將 NSData 通過 NSData(contentsOfFile: savePath) 從之前儲存的 plist 檔案中讀取。並轉化為 NSMutableDictionary
第二步重新生成一個 NSURLMutableRequest ,之所以用 Mutable,是因為方便後面自定義資訊。
第二行程式碼通過為 NSURLMutableRequest 為 HTTP 請求的頭部增加了一個 Range 欄位,這樣我們就可以從 Range 標明的位置執行下載了。
第三步重新將 NSURLMutableRequest encode 為 NSData 。
第四步講新的 URL 和 新生成的 NSData 儲存到字典中。
第五步將字典重新序列化為 NSData 。
這樣就完成了,只要每一次停止時生成一個資料,開始時讀取、修改資料。就可以在下載的URL不斷改變的情況下進行斷點續傳了。