貓貓學Swift之下載-斷點續傳
貓貓分享,必須精品
下載-斷點續傳
通過URLSession進行下載,通過OutputStream寫入檔案,通過URLSessionDataTask來控制下載的繼續暫停取消等操作
一:下載過程
1:一次完整的下載流程
1:建立request,session
//request
var request = URLRequest(url: url, cachePolicy:URLRequest.CachePolicy.reloadIgnoringLocalCacheData,
timeoutInterval: 0)
//session
let session = URLSession(configuration: URLSessionConfiguration.default , delegate: self, delegateQueue: OperationQueue.main)
2:設定下載偏移量 :offset
request.setValue(String(format: "bytes=%lld-", offset), forHTTPHeaderField: "Range")
3:根據request從會話session中拿到URLSessionDataTask任務,並且繼續執行
self.dataTask = self.session.dataTask(with: request)
self.dataTask?.resume()
4:通過session的代理方法,進行資料的傳輸下載
///主要用到了三個代理方法
/// 第一次接受到相應的時候呼叫(響應頭, 並沒有具體的資源內容)
/// 通過這個方法裡面系統提供的回撥程式碼塊(completionHandler) 可以控制:是繼續請求, 還是取消本次請求
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void);
/// 當用戶確定, 繼續接受資料的時候呼叫
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data);
/// 請求完成時候呼叫
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?);
4.1第一次接受到相應的時候,儲存要下載資源的大小,然後開啟輸出流,開始下載資料
//建立輸出流
let outputStream = OutputStream(toFileAtPath: self.tmpFilePath, append: true)
//開啟輸出流
outputStream.open()
//開始下載資料
completionHandler(.allow)
如果出現異常,name就取消本次請求,重新開始下載操作
//取消本次請求
completionHandler(.cancel)
//重新開始下載操作
self.downLoad()
4.2當用戶確定, 繼續接受資料的時候,往輸出流中寫資料,以及一些其他操作像:進行下載進度,下載速度的計算
// 往輸出流中寫入資料
data.withUnsafeBytes({ (p: UnsafePointer<UInt8>) -> Void in
outputStream.write(p, maxLength: data.count)
})
//進行下載進度,下載速度的計算
tmpSize += Int64(data.count)
//計算一秒中的速度
downTask.totalRead += Int64(data.count);
let currentDate = Date()
let time = currentDate.timeIntervalSince(downTask.lastDate)
//當前時間和上一秒時間做對比,大於等於一秒就去計算
if time >= 1 {
//計算速度
let speed = Double(downTask.totalRead) / time
//把速度轉成KB或M
downTask.speed = speed
//維護變數,將計算過的清零
downTask.totalRead = 0
//維護變數,記錄這次計算的時間
downTask.lastDate = currentDate
NYLog("------speed : \(speed)")
}
// 記錄進度
self.progress = 1.0 * Double(tmpSize) / Double(totalSize)
// 每隔downLoaderConfig.progressMinReturn 秒 閉包返回一次進度
if currentDate.timeIntervalSince(progressLastDate) > downLoaderConfig.progressMinReturn {
self.progressClosure(self.progress,tmpSize,totalSize)
progressLastDate = currentDate
}
4.3請求完成時候,成功後移動檔案,關閉輸出流,清理會話資源
2:細節:
- 下載時候用到兩個路徑:快取路徑,臨時快取路徑
- 下載的時候,將資料寫入到臨時快取路徑當中
- 下載完成,將臨時快取路徑中下載好的檔案移動到快取路徑中
- 如果快取路徑裡面有url對應的下載檔案,那就說明已經下載完成了
- 臨時快取是否下載完成, 通過對下載的檔案大小和知道的檔案大小做對比實現
3:下載檔案大小的獲取
檔案的大小可以通過響應頭(response)的Content-Length (或者Content-Range)或者伺服器給相應的欄位來獲取設定.
坑:在續傳的時候響應頭(response)的Content-Length是變化的, 於是在第一次拿到content-length的時候根據url用UserDefaults進行了一次快取,然後直接用.
ps:(這個地方可以讓後臺伺服器給,還有的做法會給一個檔案的md5,如果能有檔案的md5就不需要考慮url是否更改了之類的,當然這些屬於業務邏輯上的了,具體還需要根據自己的業務來進行分析)
二:斷點續傳原理
1:原理
斷點續傳的工作機制,在HTTP請求頭中,有一個Range的關鍵字,通過這個關鍵字可以告訴伺服器返回哪些資料。
比如:
bytes=500-999 表示第500-第999位元組
bytes=500- 表示從第500位元組往後的所有位元組
然後再根據伺服器返回的資料,將得到的data資料拼接到檔案後面,就可以實現斷點續傳了。
2:暫停和續傳
暫停和續傳網上很多都是運用了resumedata來獲取已經下載的資訊,但是在ios10 中需要做一些特殊處理, 這裡我沒有用這種方式,而是通過直接拿到本地已經下載的臨時檔案的大小,來作為對下一次資料的請求
Ps: 這裡有一個神坑
FileManager.default.attributesOfFileSystem(forPath: )
FileManager.default.attributesOfItem(atPath: )
兩個方法返回的都是[FileAttributeKey: Any] 的字典,並且第一個方法有file關鍵字,理所當然以為是用第一個,但真的應該用第二個