亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > iOS > 正文

總結iOS開發中的斷點續傳與實踐

2020-07-26 03:17:52
字體:
來源:轉載
供稿:網友

前言

斷點續傳概述

斷點續傳就是從文件上次中斷的地方開始重新下載或上傳數據,而不是從文件開頭。(本文的斷點續傳僅涉及下載,上傳不在討論之內)當下載大文件的時候,如果沒有實現斷點續傳功能,那么每次出現異?;蛘哂脩糁鲃拥臅和#紩ブ仡^下載,這樣很浪費時間。所以項目中要實現大文件下載,斷點續傳功能就必不可少了。當然,斷點續傳有一種特殊的情況,就是 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”表示服務器端接受請求資源的某一個范圍,并允許對指定資源進行字節類型訪問?!?code>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開發中的斷點續傳與實踐的全部內容,其實,下載的實現遠不止這些內容,本文只介紹了簡單的使用。希望在進一步的學習和應用中能繼續與大家分享。希望本文能幫助到有需要的大家。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产aⅴ夜夜欢一区二区三区| 国产欧美欧洲在线观看| 日本一欧美一欧美一亚洲视频| 国产97在线观看| 91久久久久久国产精品| 亚洲国产精品va在线看黑人动漫| 国产精品久久久久aaaa九色| 日韩免费在线观看视频| 国产精品美女久久久久久免费| 亚洲人成人99网站| 久久久久国色av免费观看性色| 欧美—级a级欧美特级ar全黄| 久久视频免费观看| 亚洲免费视频在线观看| 欧美成人精品激情在线观看| 亚洲精品久久久久中文字幕二区| 国产精品视频精品视频| 欧美黑人巨大xxx极品| 欧美黑人一级爽快片淫片高清| 午夜精品久久久久久久男人的天堂| 日韩免费观看视频| 亚洲免费视频网站| 日韩精品视频在线观看网址| 国产69久久精品成人看| 亚洲一区二区三区乱码aⅴ蜜桃女| 欧美日韩中国免费专区在线看| 精品国产31久久久久久| 欧美高清视频在线播放| 成人www视频在线观看| 日韩在线观看免费全集电视剧网站| 欧洲中文字幕国产精品| 91精品国产综合久久久久久久久| 亚洲欧美国产高清va在线播| 亚洲大胆人体av| 久久精品91久久香蕉加勒比| 欧美视频一区二区三区…| 91精品视频免费看| 精品在线欧美视频| 日韩电影在线观看免费| 成人黄色大片在线免费观看| 色综久久综合桃花网| 亚洲国产精品资源| 亚洲黄页网在线观看| 欧美一区二区大胆人体摄影专业网站| 91精品久久久久久久久久久久久| 欧美日韩在线免费| 精品国偷自产在线视频99| 日韩中文在线中文网在线观看| 国产亚洲精品久久久优势| 欧美最猛性xxxx| 亚洲综合自拍一区| 国产精品国语对白| 久久免费精品视频| 久久亚洲综合国产精品99麻豆精品福利| 91日韩在线播放| www国产亚洲精品久久网站| 久久久久久综合网天天| 亚洲精品视频免费在线观看| 久久精品中文字幕电影| 国产中文字幕日韩| 欧美日韩精品二区| 91九色国产在线| 国产美女主播一区| 1769国产精品| 国内精品久久久久久久| 亚洲自拍高清视频网站| 91精品久久久久久久久久久久久| 久久亚洲精品小早川怜子66| 欧美高清自拍一区| 亚洲国产另类 国产精品国产免费| 一区二区三区四区在线观看视频| 91在线免费观看网站| 亚洲xxxx视频| 欧美自拍大量在线观看| 亚洲欧美福利视频| 国语自产精品视频在线看| 成人深夜直播免费观看| 国产精品白嫩初高中害羞小美女| 欧美激情三级免费| 美日韩精品免费视频| 亚洲精品有码在线| 亚洲精品suv精品一区二区| 亚洲精品有码在线| 91产国在线观看动作片喷水| 成人啪啪免费看| 国产精品久久久久秋霞鲁丝| 中日韩午夜理伦电影免费| 国产精品三级在线| 丝袜美腿精品国产二区| 欧美大肥婆大肥bbbbb| 国产日韩视频在线观看| 97久久国产精品| 国产精品入口福利| 国产经典一区二区| 亚洲最大成人免费视频| 蜜臀久久99精品久久久无需会员| 久久精品国产久精国产思思| 国产一区二区久久精品| 国产成人精品av在线| 欧美麻豆久久久久久中文| 国产精品久久久久久影视| 91精品久久久久久久久久久久久| 91产国在线观看动作片喷水| 国产精品女人网站| 欧美午夜女人视频在线| 国产亚洲一区精品| 性色av一区二区三区红粉影视| 欧美精品videossex性护士| 亚洲91精品在线观看| 成人久久久久爱| 国产精品亚洲美女av网站| 奇米一区二区三区四区久久| 国产欧美在线视频| 欧美性猛交xxxx富婆| 亚洲精品福利免费在线观看| 日韩欧美在线视频免费观看| 国产日韩欧美日韩| 国产不卡精品视男人的天堂| 欧美成人在线免费视频| 国产欧美欧洲在线观看| 亚洲美女av在线| 狠狠色狠色综合曰曰| 国产亚洲a∨片在线观看| 欧美电影免费在线观看| 久久精品99久久久香蕉| 亚洲福利视频网| 欧美成人午夜激情在线| 亚洲国产精品网站| 欧美日韩中文字幕日韩欧美| 欧美日韩中国免费专区在线看| 亚洲三级黄色在线观看| 最新中文字幕亚洲| 在线精品91av| 国产精品国产福利国产秒拍| 成人美女免费网站视频| 欧美第一黄色网| 日韩视频精品在线| 5566日本婷婷色中文字幕97| 777国产偷窥盗摄精品视频| 色诱女教师一区二区三区| 97视频在线播放| 亚洲美女av在线| 亚洲福利视频专区| 亚洲aa在线观看| 国产成人精彩在线视频九色| 亚洲精品视频二区| 精品女厕一区二区三区| 亚洲美女免费精品视频在线观看| 亚洲精品成a人在线观看| 亚洲精品视频免费在线观看| 美女999久久久精品视频| 国产精品入口日韩视频大尺度| 尤物九九久久国产精品的特点| 亚洲国产精品久久91精品| 日韩成人网免费视频| 97香蕉久久夜色精品国产| 黑人巨大精品欧美一区二区一视频| 日韩美女中文字幕| 欧美丝袜一区二区三区| 欧美激情一区二区三区久久久| 国产91成人在在线播放| 亚洲国产日韩欧美在线动漫| 另类专区欧美制服同性| 日韩亚洲第一页|