猫猫学Swift之下载-断点续传

2018-04-09  本文已影响175人  翟乃玉

猫猫分享,必须精品

原创文章,欢迎转载。转载请注明:翟乃玉的博客
地址:http://www.jianshu.com/notebooks/4236923/latest

下载-断点续传

通过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请求完成时候,成功后移动文件,关闭输出流,清理会话资源


请求完时候成移动文件.png

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 中需要做一些特殊处理, 这里我没有用这种方式,而是通过直接拿到本地已经下载的临时文件的大小,来作为对下一次数据的请求


检测临时文件是否存在.png

Ps: 这里有一个神坑

FileManager.default.attributesOfFileSystem(forPath: )
FileManager.default.attributesOfItem(atPath: )

两个方法返回的都是[FileAttributeKey: Any] 的字典,并且第一个方法有file关键字,理所当然以为是用第一个,但真的应该用第二个

文件大小坑.png

三:其他

关于NSURLSession的一些知识可以看这篇文章
iOS中利用NSURLSession进行文件断点下载

Demo下载地址:https://github.com/znycat/NYDownLoadLib.git

上一篇下一篇

猜你喜欢

热点阅读