在處理大文件的時候,我們不可能只是單一的去下載,那么我們就需要用到斷點下載,當然你可以使用第三方實現斷點下載,但是我們有時也要知道系統自帶的怎么用,萬一你使用的第三方不維護了,就麻煩了,當然這種概率很小。今天小編主要介紹NSURLSession
實現斷點下載,NSURLConnection
也可以實現,NSURLConnection
在iOS9被蘋果廢棄了,所以還是使用NSURLSession
吧,下面我們先看下效果。
斷點下載思路
先介紹一下單任務下載,實現方式
- NSMutableData拼接
使用會消耗大量內存,不用這個 - NSURLConnection
iOS9被廢棄了也不用 - NSURLSessionDataTask
使用方式和NSURLConnection
差不多,需要我們自己實現下載路徑 - NSURLSessionDownloadTask
使用很方便,默認下載到tmp文件
我所知道的就這四種,歡迎大家補充,我是使用NSURLSessionDataTask
實現的。
創建Session,NSURLSessionConfiguration
是配置信息,使用默認的
func initSession() -> URLSession {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.liuchang.cn")
let session = Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
return session;
}
獲取NSURLSessionDataTask
需要手動開始
let dataTask = session.dataTaskWithRequest(request)
dataTask.resume()
下面是用到的代理方法
收到響應。需要注意的是需要把NSURLSessionResponseDisposition
設為Allow,
如果NSHTTPURLResponse
的expectedContentLength = -1
需要服務員設置大小.
// 收到響應
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
這個方法會多次調用,在這里把收到的數據存到本地,使用NSOutputStream
可以實現拼接數據到本地。這里需要注意一下,OC中默認使用了多線程,在swift中默認在主線程執行
// 獲取data 會多次調用
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)
下載成功或者失敗會調用,在這里關閉NSOutputStream
// 下載完成
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
多任務斷點下載思路
既然有多個任務要么數組要么字典,但是在這里使用數組沒有字典容易控制數據,我們需要創建一個model,把model保存到字典
enum LCDownloadState {
case Running // 下載中
case Suspended // 暫停
case Canceled // 取消
case Completed // 下載完成
case Failed // 下載失敗
}
class LCDownload: NSObject {
var dataTask: NSURLSessionDataTask?
var outputStream: NSOutputStream?
var allLength: Int = 0
var progressBlock: ((progress: CGFloat) -> Void)?
var stateBlock: ((state: LCDownloadState) -> Void)?
}
下面我們需要創建一個單例,這也是為了用戶可以邊下載邊瀏覽其他內容
private static let sDownload = LCSwiftDownload()
class var sharedInstance: LCSwiftDownload {
return sDownload
}
添加下載任務,根據字典判斷是否有下載任務,沒有就創建session,這里請求的使用添加Range
是為了殺死程序時,下次再進來的時候根據本地已經下載的大小去請求數據,就不需要重新請求。這里使用KVC修改taskIdentifier
為了在代理中判斷是哪個任務。
/**
在點擊事件中使用
- parameter url: url
- parameter tag: 唯一標識
- parameter resume: 是否開始下載
- parameter progerss: 進度 可以為nil
- parameter state: 狀態 可以為nil
*/
func downloadData(url: String, tag: Int, resume: Bool, progerss: ((Float) -> Void)?, state: ((LCDownloadState) -> Void)?) {
let fileLength = getFileDataDownloadedLength(tag: tag)
let allLength = getAllLength(tag: tag)
if fileLength > 0 && fileLength == allLength {
state?(.Completed)
progerss?(1.0)
return
}
let tagStr = String(tag)
if let download = downloadDic[tagStr] {
let dataTask = download.dataTask
if resume {
dataTask?.resume()
download.stateBlock?(.Running)
}else {
dataTask?.suspend()
download.stateBlock?(.Suspended)
}
}else {
var request = URLRequest(url: URL(string: url)!)
let session = initSession()
request.setValue("bytes=\(fileLength)-", forHTTPHeaderField: "Range")
let dataTask = session.dataTask(with: request)
dataTask.setValue(tag, forKey: "taskIdentifier")
let download = LCDownload()
download.dataTask = dataTask
download.progressBlock = progerss
download.stateBlock = state
downloadDic[tagStr] = download
if resume {
dataTask.resume()
}
}
}
下面是代理方法:
收到響應時主要是獲取總大小并保存到沙盒,使用expectedContentLength
能直接獲取請求總大小,并且開啟outputStream
// 收到響應
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
let response = response as! HTTPURLResponse
let allLength = Int(response.expectedContentLength) + getFileDataDownloadedLength(tag: dataTask.taskIdentifier)
setAllLength(length: Int64(allLength), WithTag: dataTask.taskIdentifier)
if let download = downloadDic[String(dataTask.taskIdentifier)] {
let path = initFileDataCachePath(tag: dataTask.taskIdentifier)
print(path)
download.outputStream = OutputStream.init(toFileAtPath: path, append: true)
download.outputStream!.open()
download.allLength = Int(allLength)
}
completionHandler(.allow)
}
在這里把獲取的數據保存到沙盒,并且根據本地下載數據獲取下載比例,UnsafePointer
是swift中的指針,用的很少。
// 獲取data 會多次調用
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if let download = downloadDic[String(dataTask.taskIdentifier)] {
let downloadedLength = getFileDataDownloadedLength(tag: dataTask.taskIdentifier)
let dataMutablePointer = UnsafeMutablePointer<UInt8>.allocate(capacity: data.count)
//Copies the bytes to the Mutable Pointer
data.copyBytes(to: dataMutablePointer, count: data.count)
//Cast to regular UnsafePointer
let dataPointer = UnsafePointer<UInt8>(dataMutablePointer)
//Your stream
download.outputStream?.write(dataPointer, maxLength: data.count)
let progress = Float(downloadedLength) / Float(download.allLength)
download.stateBlock?(.Running)
download.progressBlock?(progress)
}
}
下載完成關閉outputStream并且刪除完成的model
// 下載完成
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let download = downloadDic[String(task.taskIdentifier)] {
download.stateBlock?(.Completed)
download.progressBlock?(1.0)
download.outputStream?.close()
download.outputStream = nil
downloadDic.removeValue(forKey: String(task.taskIdentifier))
if error != nil {
download.stateBlock?(.Failed)
}
}
}
以上是主要的思路,在這里下載代碼代碼中包括OC和Swift,有寫地方需要優化的地方希望大家指出
最后更新內容:
- OC代碼下載唯一標識改用URL
- 適配swift4.2