作为一个精益求精的项目,在牵涉到下载操作时 ,为了提高用户体验,提高下载效率,不可避免的会使用到后台下载,尤其是下载文件较大时。那么我们从原生下载 URLSession 到 Alamofire 的后台下载封装循序渐进的讲解swift的后台下载。
一、URLSession后台下载
后台下载代码如下
//初始化一个后台下载的配置类URLSessionConfiguration
let configuration = URLSessionConfiguration.background(withIdentifier: "com.ios.xiaoqiang.download")
//初始化网络下载会话
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
//创建下载task,resume启动该task
session.downloadTask(with: URL(string: self.urlDownloadStr2)!).resume()
1. 创建URLSessionConfiguration
URLSessionConfiguration有三种模式:
- default:默认模式,通常我们用这种模式就足够了。default 模式下系统会创建一个持久化的缓存并在用户的钥匙串中存储证书
- ephemeral:系统没有任何持久性存储,所有内容的生命周期都与 session 相同,当 session 无效时,所有内容自动释放。
- background: 创建一个可以在后台甚至 APP 已经关闭的时候仍然在传输数据的会话。
2. 通过configuration创建网络会话
设置代理回调对象和代理回调队列
3. 创建task
task需要手动调动resume启动下载任务。
4. 设置代理回调
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// 下载完成 - 开始沙盒迁移
print("下载完成 - \(location)")
let locationPath = location.path
//拷贝到用户目录(文件名以时间戳命名)
let documnets = NSHomeDirectory() + "/Documents/" + self.lgCurrentDataTurnString() + ".mp4"
print("移动地址:\(documnets)")
//创建文件管理器
let fileManager = FileManager.default
try! fileManager.moveItem(atPath: locationPath, toPath: documnets)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
print(" bytesWritten \(bytesWritten)\n totalBytesWritten \(totalBytesWritten)\n totalBytesExpectedToWrite \(totalBytesExpectedToWrite)")
print("下载进度: \(Double(totalBytesWritten)/Double(totalBytesExpectedToWrite))\n")
}
- 实现didFinishDownloadingTo代理方法,将请求回来的数据写入沙盒中
- 通过didWriteData回调监听下载进度。
由于我们需要后台下载,那么我们就需要做额外的操作处理,根据苹果文档Downloading Files in the Background中的说明,我们需要实现以下方法
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//保存后台下载的completionHandler
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
self.backgroundSessionCompletionHandler = completionHandler
}
}
5. AppDelegate的回调
在appdelegate中实现handleEventsForBackgroundURLSession回调,并且保存完成block
实现的目的是为了在应用程序在后台下载完成时,切到前台有回调告知下载已完成,并且刷新UI。不实现控制台会爆如下警告
Warning: Application delegate received call to -
application:handleEventsForBackgroundURLSession:completionHandler:
but the completion handler was never called.
6. 调用AppDelegate的block
实现URLSessionDelegate的urlSessionDidFinishEvents后台完成下载回调,在回调中获取并调用保存的appdelegate的代理回调,
- 注意:UI刷新操作需要在主队列调用
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("后台任务下载回来")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
backgroundHandle()
}
}
二、Alamofire后台下载
1. Alamofire 先创建一个XQBackgroundManger单利类
目的如下
- 用于方便与appdelegate的后台下载回调进行交互
- 持有SessionManager,不会导致下载时,对象被释放
- 应用层与网络层也可以达到分离
- 自定义配置请求为background模式
struct XQBackgroundManger {
static let shared = XQBackgroundManger()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.xiaoqiang.AlamofireTest.demo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
}
2. 触发下载
代码如下
XQBackgroundManger.shared.manager
.download(self.urlDownloadStr) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let fileUrl = documentUrl?.appendingPathComponent(response.suggestedFilename!)
return (fileUrl!,[.removePreviousFile,.createIntermediateDirectories])
}
.response { (downloadResponse) in
print("下载回调信息: \(downloadResponse)")
}
.downloadProgress { (progress) in
print("下载进度 : \(progress)")
}
3. 配置下载回调
在.download(self.urlDownloadStr) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in }中进行数据迁移操作,此处返回一个元组类型,第一个参数为文件url,第二个参数为DownloadOptions元素类型的数组。
4. 在appdelegate中的回调保存
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
XQBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}
此处可以更方便的保存回调对象。
三、Alamofire下载流程封装
作为一名合格的开发者,肯定要做到知其然更知其所以然,下面我们就来分析一下 Alamofire 如何封装网络请求的。
1. 下载创建
public static let `default`: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
使用SessionManager.default创建时,配置默认请求header
public init(
configuration: URLSessionConfiguration = URLSessionConfiguration.default,
delegate: SessionDelegate = SessionDelegate(),
serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
self.delegate = delegate
self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {
session.serverTrustPolicyManager = serverTrustPolicyManager
delegate.sessionManager = self
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
guard let strongSelf = self else { return }
DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
}
}
- 初始化default模式的URLSession。
- 创建配置代理类SessionDelegate
2. 代理事件回调
SessionDelegate实现如下代理方法
- URLSessionDelegate
- URLSessionTaskDelegate
- URLSessionDataDelegate
- URLSessionDownloadDelegate
- URLSessionStreamDelegate
SessionDelegate是用于实现网络回调代理的方法,并且将对应的操作回调给sessionManager,这样就可以避免sessionManager中有太多事件回调,sessionManager只需要做事件调用管理即可。
- 代理类weak持有sessionManager,用于事件回调
- 赋值delegate.sessionDidFinishEventsForBackgroundURLSession
SessionDelegate中的urlSessionDidFinishEvents实现如下,调用初始化赋值的block,
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
sessionManager初始化赋值如下,sessionDidFinishEventsForBackgroundURLSession会异步调度到主队列执行外部的backgroundCompletionHandler,即Appdelegate的handleEventsForBackgroundURLSession的回调block
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
guard let strongSelf = self else { return }
DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
}
Alamofire 是封装的苹果原生 URLSession 进行下载,对繁琐的网络请求配置和回调进行下沉封装,便于开发者进行更为简洁的调用,使得开发者更加专注与业务开发上。优秀的第三方框架提供更加简洁,方便的API,下沉复杂的配置和回调操作,这就是我们使用第三方框架的原因。