猫猫分享,必须精品
原创文章,欢迎转载。转载请注明:翟乃玉的博客
地址:http://write.blog.csdn.net/postlist
下载-断点续传
通过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关键字,理所当然以为是用第一个,但真的应该用第二个
三:其他
关于NSURLSession的一些知识可以看这篇文章
iOS中利用NSURLSession进行文件断点下载