内容来自学习PART 6: DOWNLOAD AND UPLOAD TASK等系列课程,记录学习笔记
Networking with URLSession二 上传&下载
下载和上传Task
URLSessionDownloadTask
URLSessionDownloadTask直接把服务器的响应写入到一个临时文件中,并提供进度更新。如果在background session中使用download task,即使app程序被暂停或未运行,这些下载也会继续下载。
URLSession
创建download task的方式
func downloadTask(with: URL,completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void)
func downloadTask(with: URLRequest,completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void)
downloadTask把数据保存在一个临时的文件中,所以在completion handler返回之前,必须读取和处理文件,或者把文件copy到一个永久的位置。否则文件会被删除,data会丢失。
func downloadTask(with: URL)
func downloadTask(with: URLRequest)
在调用过程中:
- session会周期性的调用代理方法urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:),提供一些状态信息
- 在成功时调用urlSession(_:downloadTask:didFinishDownloadingTo:)方法或者一个completion回调。在这个方法中,你要么打开这个文件来读取,要么把这个文件移动到沙盒中的一个永久的位置
- 失败时调用urlSession(_:task:didCompleteWithError:)方法或者一个completion回调
一个download task如果在完成之前被取消或者下载失败,可以保存resume data,并继续下载。
func cancel(byProducingResumeData:@escaping (Data?) -> Void)
func downloadTask(withResumeData: Data,completionHandler: @escaping (URL?,URLResponse?, Error?) -> Void)
func downloadTask(withResumeData: Data)
与URLSessionDataTask、URLSessionUploadTask不同,NSURLSessionDownloadTask
通过HTTP状态码,把服务器错误,报告给响应的NSError
对象:
Server-side errors | NSErrors |
---|---|
401 Unauthorized | NSURLErrorUserAuthenticationRequired |
403 Forbidden | NSURLErrorNoPermissionsToReadFile |
407 Proxy Authentication Required | NSURLErrorUserAuthenticationRequired |
Other | NSURLErrorFileDoesNotExist |
URLSessionUploadTask
URLSessionUploadTask与download task类似。可直接通过completionHandler来处理结果,而不通过代理
如下,上传data数据:
func uploadTask(with: URLRequest, from: Data?,completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void)
func uploadTask(with: URLRequest, from: Data)
但是不能直接使用URL来创建upload task,需要通过URLRequest来创建。因为upload task的http请求,需要http body。
除了上传data数据外,还可以上传file:
func uploadTask(with: URLRequest, fromFile: URL,completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void)
func uploadTask(with: URLRequest, fromFile: URL)
另外还有一个stream request
func uploadTask(withStreamedRequest: URLRequest)
各种优先级
1.URLSessionConfiguration
的networkServiceType
属性对标准的网络流量,网络电话,语音,视频,以及由一个后台进程使用的流量进行了区分。值可为default, voip, video, background, voice
2.URLSessionTask
的priority
属性。即你希望host处理任务的相对优先级。0.0 (lowest)
到 1.0 (highest)
, default
是0.5
3.URLRequest
的networkServiceType
。可重写配置对象的networkServiceType
4.URLSession’s delegateQueue
qualityOfService: userInteractive, userInitiated, utility, background
缓存
大量的下载可以很好的利用缓存来减少网络拥堵的问题。Default configuration使用的是一个持久的基于disc的cache。
cache会减少app对网络连接的依赖,提高的app的性能。
默认的cache policy使用的是protocol的cache policy,而protocol通常是HTTP。
参考文档:
下载
本例下载,是下载音频示例,如:
http://audio.itunes.apple.com/apple-assets-us-std-000001/AudioPreview30/v4/02/4e/35/024e3534-92b4-6e6d-217e-b5714b6faa20/mzaf_5572571419358472776.plus.aac.p.m4a
开始下载资源
代码如下:
func startDownload(_ track: Track) {
let download = Download(url: track.previewURL)
//下载任务
download.task = defaultSession.downloadTask(with: track.previewURL) { location, response, error in
self.saveDownload(download: download, location: location, response: response, error: error)
}
download.task!.resume()
download.isDownloading = true
activeDownloads[download.url] = download
}
func saveDownload(download : Download, location : URL?, response : URLResponse?, error : Error?) {
let sourceURL = download.url
if error != nil { return }
activeDownloads[sourceURL] = nil
//copy目的地地址
let destinationURL = localFilePath(for: sourceURL)
//先移除在复制
let fileManager = FileManager.default
try? fileManager.removeItem(at: destinationURL)
do {
try fileManager.copyItem(at: location!, to: destinationURL)
} catch let error {
print("Could not copy file to disk: \(error.localizedDescription)")
}
//更新UI
if let index = trackIndex(for: download.task!) {
DispatchQueue.main.async {
self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none)
}
}
}
暂停下载
//暂停下载
func pauseDownload(_ track: Track) {
guard let download = activeDownloads[track.previewURL] else { return }
//处理已下载的数据
download.task!.cancel(byProducingResumeData: { (data) in
download.resumeData = data
})
download.isDownloading = false
//更新UI
if let index = trackIndex(for: download.task!) {
DispatchQueue.main.async {
self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none)
}
}
}
取消下载
//取消下载
func cancelDownload(_ track: Track) {
guard let download = activeDownloads[track.previewURL] else { return }
//取消下载
download.task!.cancel()
download.isDownloading = false
activeDownloads[track.previewURL] = nil
if let index = trackIndex(for: download.task!) {
DispatchQueue.main.async {
self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none)
}
}
}
继续下载
// 继续下载
func resumeDownload(_ track: Track) {
guard let download = activeDownloads[track.previewURL] else { return }
if let resumeData = download.resumeData {
//在原来的数据上继续下载
download.task = defaultSession.downloadTask(withResumeData: resumeData, completionHandler: { (location, response, error) in
self.saveDownload(download: download, location: location, response: response, error: error)
})
}else {
//重新下载
download.task = defaultSession.downloadTask(with: download.url, completionHandler: { (location, response, error) in
self.saveDownload(download: download, location: location, response: response, error: error)
})
}
download.task!.resume()
download.isDownloading = true
if let index = trackIndex(for: download.task!) {
DispatchQueue.main.async {
self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none)
}
}
}
上传
上传以上一节的PostRouter
为例,上传json数据
let session = URLSession(configuration: .ephemeral)
let putRequest = PostRouter.update(1, ["author": "Part 6", "title": "Upload Task"]).asURLRequest()
typealias JSONDictionary = [String: Any]
let putTask = session.uploadTask(with: putRequest, from: putRequest.httpBody) { data, response, error in
defer { PlaygroundPage.current.finishExecution() }
guard let data = data, let response = response as? HTTPURLResponse,
response.statusCode == 200 else {
print("No data or statusCode not OK")
return
}
var jsonResult: JSONDictionary
do {
let result = try JSONSerialization.jsonObject(with: data, options: [])
jsonResult = result as! JSONDictionary
} catch {
print(error.localizedDescription)
return
}
}
putTask.resume()