1. 程式人生 > >貓貓學Swift之下載-斷點續傳

貓貓學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:細節:

  1. 下載時候用到兩個路徑:快取路徑,臨時快取路徑
  2. 下載的時候,將資料寫入到臨時快取路徑當中
  3. 下載完成,將臨時快取路徑中下載好的檔案移動到快取路徑中
  4. 如果快取路徑裡面有url對應的下載檔案,那就說明已經下載完成了
  5. 臨時快取是否下載完成, 通過對下載的檔案大小和知道的檔案大小做對比實現

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關鍵字,理所當然以為是用第一個,但真的應該用第二個

檔案大小坑

三:其他