iOS 下載URL不斷改變的情況下 使用 resumeData做斷點續傳

盧三發表於2017-12-20

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不斷改變的情況下進行斷點續傳了。

相關文章