媒介
斷點續傳概述
斷點續傳就是從文件前次中止的處所開端從新下載或上傳數據,而不是從文件開首。(本文的斷點續傳僅觸及下載,上傳不在評論辯論以內)當下載年夜文件的時刻,假如沒有完成斷點續傳功效,那末每次湧現異常或許用戶自動的暫停,都邑去重頭下載,如許很糟蹋時光。所以項目中要完成年夜文件下載,斷點續傳功效就必弗成少了。固然,斷點續傳有一種特別的情形,就是 IOS 運用被用戶 kill 失落或許運用 crash,要完成運用重啟以後的斷點續傳。這類特別情形是本文要處理的成績。
斷點續傳道理
要完成斷點續傳 , 辦事器必需支撐。今朝最多見的是兩種方法:FTP 和 HTTP。
上面來簡略引見 HTTP 斷點續傳的道理。
HTTP
經由過程 HTTP,可以異常便利的完成斷點續傳。斷點續傳重要依附於 HTTP 頭部界說的 Range 來完成。在要求某規模內的資本時,可以更有用地對年夜資本收回要求或從傳輸毛病中恢復下載。有了 Range,運用可以經由過程 HTTP 要求已經獲得掉敗的資本的某一個前往或許是部門,來恢復下載該資本。固然其實不是一切的辦事器都支撐 Range,但年夜多半辦事器是可以的。Range 是以字節盤算的,要求的時刻不用給出開頭字節數,由於要求方其實不必定曉得資本的年夜小。
Range 的界說如圖 1 所示:
圖 1. HTTP-Range

圖 2 展現了 HTTP request 的頭部信息:
圖 2. HTTP request 例子

在下面的例子中的“Range: bytes=1208765-”表現要求資本開首 1208765 字節以後的部門。
圖 3 展現了 HTTP response 的頭部信息:
圖 3. HTTP response 例子

下面例子中的”Accept-Ranges: bytes”表現辦事器端接收要求資本的某一個規模,並許可對指定資本停止字節類型拜訪。”Content-Range: bytes 1208765-20489997/20489998”解釋了前往供給了要求資本地點的原始實體內的地位,還給出了全部資本的長度。這裡須要留意的是 HTTP return code 是 206 而不是 200。
斷點續傳剖析 -AFHTTPRequestOperation
懂得了斷點續傳的道理以後,我們便可以著手來完成 IOS 運用中的斷點續傳了。因為筆者項目標資本都是安排在 HTTP 辦事器上 , 所以斷點續傳功效也是基於 HTTP 完成的。起首來看下第三方收集框架 AFNetworking 中供給的完成。清單 1 示例代碼是用來完成斷點續傳部門的代碼:
清單 1. 應用 AFHTTPRequestOperation 完成斷點續傳的代碼
// 1 指定下載文件地址 URLString
// 2 獲得保留的文件途徑 filePath
// 3 創立 NSURLRequest
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:URLString]];
unsigned long long downloadedBytes = 0;
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
// 3.1 若之前下載過 , 則在 HTTP 要求頭部參加 Range
// 獲得已下載文件的 size
downloadedBytes = [self fileSizeForPath:filePath];
// 驗證能否下載過文件
if (downloadedBytes > 0) {
// 若下載過 , 斷點續傳的時刻修正 HTTP 頭部部門的 Range
NSMutableURLRequest *mutableURLRequest = [request mutableCopy];
NSString *requestRange =
[NSString stringWithFormat:@"bytes=%llu-", downloadedBytes];
[mutableURLRequest setValue:requestRange forHTTPHeaderField:@"Range"];
request = mutableURLRequest;
}
}
// 4 創立 AFHTTPRequestOperation
AFHTTPRequestOperation *operation
= [[AFHTTPRequestOperation alloc] initWithRequest:request];
// 5 設置操作輸入流 , 保留在第 2 步的文件中
operation.outputStream = [NSOutputStream
outputStreamToFileAtPath:filePath append:YES];
// 6 設置下載進度處置 block
[operation setDownloadProgressBlock:^(NSUInteger bytesRead,
long long totalBytesRead, long long totalBytesExpectedToRead) {
// bytesRead 以後讀取的字節數
// totalBytesRead 讀取的總字節數 , 包括斷點續傳之前的
// totalBytesExpectedToRead 文件總年夜小
}];
// 7 設置 success 和 failure 處置 block
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation
*operation, id responSEObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
// 8 啟動 operation
[operation start];
應用以上代碼 , 斷點續傳功效就完成了,運用從新啟動或許湧現異常情形下 , 都可以基於曾經下載的部門開端持續下載。症結的處所就是把曾經下載的數據耐久化。接上去簡略看下 AFHTTPRequestOperation 是怎樣完成的。經由過程檢查源碼 , 我們發明 AFHTTPRequestOperation 繼續自 AFURLConnectionOperation , 而 AFURLConnectionOperation 完成了 NSURLConnectionDataDelegate 協定。
處置流程如圖 4 所示:
圖 4. AFURLHTTPrequestOperation 處置流程

這裡 AFNetworking 為何采用子線程調異步接口的方法 , 是由於直接在主線程挪用異步接口 , 會有一個 Runloop 的成績。當主線程挪用 [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES] 時 , 要求收回以後的監聽憑務會參加到主線程的 Runloop 中 ,RunloopMode 默許為 NSDefaultRunLoopMode, 這個表現只要以後線程的 Runloop 處置 NSDefaultRunLoopMode 時,這個義務才會被履行。而當用戶在轉動 TableView 和 ScrollView 的時刻,主線程的 Runloop 處於 NSEventTrackingRunLoop 形式下,就不會履行 NSDefaultRunLoopMode 的義務。
別的因為采用子線程挪用接口的方法 , 所以這邊的 DownloadProgressBlock,success 和 failure Block 都須要回到主線程來處置。
斷點續傳實戰
懂得了道理和 AFHTTPRequestOperation 的例子以後 , 來看下完成斷點續傳的三種方法:
NSURLConnection
基於 NSURLConnection 完成斷點續傳 , 症結是知足 NSURLConnectionDataDelegate 協定,重要完成了以下三個辦法:
清單 2. NSURLConnection 的完成
// SWIFT
// 要求掉敗處置
func connection(connection: NSURLConnection,
didFailWithError error: NSError) {
self.failureHandler(error: error)
}
// 吸收到辦事器呼應是挪用
func connection(connection: NSURLConnection,
didReceiveResponse response: NSURLResponse) {
if self.totalLength != 0 {
return
}
self.writeHandle = NSFileHandle(forWritingAtPath:
FileManager.instance.cacheFilePath(self.fileName!))
self.totalLength = response.expectedContentLength + self.currentLength
}
// 當辦事器前往實體數據是挪用
func connection(connection: NSURLConnection, didReceiveData data: NSData) {
let length = data.length
// move to the end of file
self.writeHandle.seekToEndOfFile()
// write data to sanbox
self.writeHandle.writeData(data)
// calculate data length
self.currentLength = self.currentLength + length
print("currentLength\(self.currentLength)-totalLength\(self.totalLength)")
if (self.downloadProgressHandler != nil) {
self.downloadProgressHandler(bytes: length, totalBytes:
self.currentLength, totalBytesExpected: self.totalLength)
}
}
// 下載終了後挪用
func connectionDidFinishLoading(connection: NSURLConnection) {
self.currentLength = 0
self.totalLength = 0
//close write handle
self.writeHandle.closeFile()
self.writeHandle = nil
let cacheFilePath = FileManager.instance.cacheFilePath(self.fileName!)
let documenFilePath = FileManager.instance.documentFilePath(self.fileName!)
do {
try FileManager.instance.moveItemAtPath(cacheFilePath, toPath: documenFilePath)
} catch let e as NSError {
print("Error occurred when to move file: \(e)")
}
self.successHandler(responSEObject:fileName!)
}
如圖 5 所示 , 解釋了 NSURLConnection 的普通處置流程。
圖 5. NSURLConnection 流程

依據圖 5 的普通流程,在 didReceiveResponse 中初始化 fileHandler, 在 didReceiveData 中 , 將吸收到的數據耐久化的文件中 , 在 connectionDidFinishLoading 中,清空數據和封閉 fileHandler,並將文件保留到 Document 目次下。所以當要求湧現異常或運用被用戶殺失落,都可以經由過程耐久化的中央文件來斷點續傳。初始化 NSURLConnection 的時刻要留意設置 scheduleInRunLoop 為 NSRunLoopCommonModes,否則就會湧現進度條 UI 沒法更新的景象。
完成後果如圖 6 所示:
圖 6. NSURLConnection 演示

NSURLSessionDataTask
蘋果在 IOS7 開端,推出了一個新的類 NSURLSession, 它具有了 NSURLConnection 所具有的辦法,而且更壯大。因為經由過程 NSURLConnection 從 2015 年開端被棄用了,所以讀者推舉基於 NSURLSession 去完成續傳。NSURLConnection 和 NSURLSession delegate 辦法的映照關系 , 如圖 7 所示。所以症結是要知足 NSURLSessionDataDelegate 和 NSURLsessionTaskDelegate。
圖 7. 協定之間映照關系

代碼如清單 3 所示 , 根本和 NSURLConnection 完成的一樣。
清單 3. NSURLSessionDataTask 的完成
// SWIFT
// 吸收數據
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask,
idReceiveData data: NSData) {
//. . .
}
// 吸收辦事器呼應
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask,
didReceiveResponse response: NSURLResponse, completionHandler:
(NSURLSessionResponseDisposition) -> Void) {
// . . .
completionHandler(.Allow)
}
// 要求完成
func URLSession(session: NSURLSession, task: NSURLSessionTask,
didCompleteWithError error: NSError?) {
if error == nil {
// . . .
self.successHandler(responSEObject:self.fileName!)
} else {
self.failureHandler(error:error!)
}
}
差別在與 didComleteWithError, 它將 NSURLConnection 中的 connection:didFailWithError:
和 connectionDidFinishLoading: 整合到了一路 , 所以這邊要依據 error 辨別履行勝利的 Block 和掉敗的 Block。
完成後果如圖 8 所示:
圖 8. NSURLSessionDataTask 演示

NSURLSessionDownTask
最初來看下 NSURLSession 頂用來下載的類 NSURLSessionDownloadTask,對應的協定是 NSURLSessionDownloadDelegate,如圖 9 所示:
圖 9. NSURLSessionDownloadDelegate 協定

個中在加入 didFinishDownloadingToURL 後,會主動刪除 temp 目次下對應的文件。所以有關文件操作必需要在這個辦法外面處置。之前筆者曾想找到這個 tmp 文件 , 基於這個文件做斷點續傳 , 無法一向找不到這個文件的途徑。等今後 SWIFT 頒布 NSURLSession 的源碼以後,也許會無方法找到。基於 NSURLSessionDownloadTask 來完成的話 , 須要在 cancelByProducingResumeData 中保留曾經下載的數據。進度告訴就異常簡略了,直接在 URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite: 完成便可。
代碼如清單 4 所示:
清單 4. NSURLSessionDownloadTask 的完成
//SWIFT
//UI 觸發 pause
func pause(){
self.downloadTask?.cancelByProducingResumeData({data -> Void in
if data != nil {
data!.writeToFile(FileManager.instance.cacheFilePath(self.fileName!),
atomically: false)
}
})
self.downloadTask = nil
}
// MARK: - NSURLSessionDownloadDelegate
func URLSession(session: NSURLSession, downloadTask:
NSURLSessionDownloadTask, didWriteData bytesWritten: Int64,
totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if (self.downloadProgressHandler != nil) {
self.downloadProgressHandler(bytes: Int(bytesWritten),
totalBytes: totalBytesWritten, totalBytesExpected: totalBytesExpectedToWrite)
}
}
func URLSession(session: NSURLSession, task: NSURLSessionTask,
didCompleteWithError error: NSError?) {
if error != nil {//real error
self.failureHandler(error:error!)
}
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask,
didFinishDownloadingToURL location: NSURL) {
let cacheFilePath = FileManager.instance.cacheFilePath(self.fileName!)
let documenFilePath = FileManager.instance.documentFilePath(self.fileName!)
do {
if FileManager.instance.fileExistsAtPath(cacheFilePath){
try FileManager.instance.removeItemAtPath(cacheFilePath)
}
try FileManager.instance.moveItemAtPath(location.path!, toPath: documenFilePath)
} catch let e as NSError {
print("Error occurred when to move file: \(e)")
}
self.successHandler(responseObject:documenFilePath)
}
完成後果如圖 10 所示:
圖 10. NSURLSessionDownloadTask 演示

總結
以上就是本文總結iOS開辟中的斷點續傳與理論的全體內容,其實,下載的完成遠不止這些內容,本文只引見了簡略的應用。願望在進一步的進修和運用中能持續與年夜家分享。願望本文能贊助到有須要的年夜家。
【總結iOS開辟中的斷點續傳與理論】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!