在开发中,为了提高用户体验经常会把下载大文件等网络请求放到后台下载,这样即便用户进入了后台,任务也能继续进行。那么这篇文章就来讨论下如何使用Apple原生框架URLSession
的API和基于URLSession
的第三方框架Alamofire
来实现后台下载功能。编程
1、 首先发起一个background
模式的请求:api
// 初始化一个background的模式的configuration. Identifier:配置对象的惟一标识符
let configuration = URLSessionConfiguration.background(withIdentifier: "com.test")
// 建立session
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
// 建立downloadTask任务,而后resume启动
session.downloadTask(with: URL(string: urlStr).resume()
复制代码
URLSessionConfiguration
有三种模式,只有background
的模式才能进行后台下载。
default
:默认模式,一般咱们用这种模式就足够了。default
模式下系统会建立一个持久化的缓存并在用户的钥匙串中存储证书。ephemeral
:系统没有任何持久性存储,全部内容的生命周期都与 session
相同,当 session
无效时,全部内容自动释放。background
: 建立一个能够在后台甚至APP已经关闭的时候仍然在传输数据的会话。background
模式与 default
模式很是类似,只不过 background
模式会用一个独立线程来进行数据传输。background
模式能够在程序挂起,退出,崩溃的状况下运行 task
,也能够利用标识符来恢复进。注意,后台 Session
必定要是惟一的 identifier
,这样在 APP
下次运行的时候,可以根据 identifier
来进行相关的区分。若是用户关闭了 APP
, iOS
系统会关闭全部的background Session
。并且,被用户强制关闭了之后,iOS
系统不会主动唤醒 APP
,只有用户下次启动了 APP
,数据传输才会继续。2、 实现相关代理回调缓存
extension ViewController:URLSessionDownloadDelegate {
// 下载完成回调
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
let locationPath = location.path
let documnets = NSHomeDirectory() + "/Documents/" + "\(Date().timeIntervalSince1970)" + ".mp4"
let fileManager = FileManager.default
//拷贝到用户目录
try! fileManager.moveItem(atPath: locationPath, toPath: documnets)
}
// 监听下载进度。http分片断传输,因此这个代理会回调屡次
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")
}
}
复制代码
URLSessionDownloadDelegate
的两个代理方法来监听和处理下载的数据。3、AppDelegate中处理后台下载回调闭包bash
Appdelegate
中实现handleEventsForBackgroundURLSession
回调,而且保存完成block。class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//用于保存后台下载的completionHandler
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
self.backgroundSessionCompletionHandler = completionHandler
}
}
复制代码
URLSessionDownloadDelegate
代理的urlSessionDidFinishEvents
方法,而且在主线程中执行在AppDelegate
中保存的block。注意线程切换主线程,由于会刷新界面。extension ViewController:URLSessionDownloadDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("后台任务下载回来")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
backgroundHandle()
}
}
}
复制代码
URLSession
对象关联的后台传输完成后调用此方法,不管传输成功完成仍是致使错误。handleEventsForBackgroundURLSession
方法的completionHandler
回调,这是很是重要的。告诉系统后台下载回来及时刷新屏幕。URLSessionDownloadDelegate
的代理方法不会再收到 Task
相关的消息。当全部 Task
全都完成后,系统才会调用 AppDelegate
的application:handleEventsForBackgroundURLSession:completionHandler:
回调。urlSessionDidFinishEvents
这个代理方法中不执行保存在AppDelegate
里面的blcok,会致使刚进入前台时页面卡顿,影响用户体验,同时还会打印警告信息。1、建立一个下载任务网络
BackgroundManger.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)")
}
复制代码
BackgroundManger
,它是一个单例。若是你配置出来不作成单利,或者不被持有。那么会在进入后台后就会释放,网络也就会报错:Error Domain=NSURLErrorDomain Code=-999 "cancelled"
AppDelegate
中的application:handleEventsForBackgroundURLSession:completionHandler:
方法的回调Alamofire
框架使用链式编程,写起来很是的方便简洁,逻辑清晰,可读性强。struct BackgroundManger {
static let shared = BackgroundManger()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.text.demo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
}
复制代码
2、处理AppDelegate
中的回调session
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
BackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}
复制代码
URLSession
实现后台下载时的做用同样。这行代码很是重要,必定要加上,否则下载完成后进入前台会有掉帧的状况,影响用户体验。3、SessionManger流程分析 Alamofire
使用起来很是简单,那么它内部是怎样帮咱们处理一些繁琐的事情的呢,下面一块儿来分析下:闭包
SessionManger
初始化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)
}
复制代码
configuration
初始化了session
对象。SessionDelegate
这个专门处理代理的类来实现 URLSession
的代理。这样能够达到业务下沉,便于阅读和解耦,每一个类只需负责本身的任务,分工明确,不至于臃肿。SessionDelegate
类中实现的代理有URLSessionDelegate``URLSessionTaskDelegate``URLSessionDataDelegate``URLSessionDownloadDelegate``URLSessionStreamDelegate
urlSessionDidFinishEvents
这个代理方法open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
复制代码
sessionDidFinishEventsForBackgroundURLSession
闭包,那么这个闭包在何时赋值的呢?由于SessionDelegate
这个类是专门处理代理的类,不处理其余逻辑,因此这个block应该是管理类SessionManger
来处理的。这是一种很是重要的设计思惟。SessionManger
初始化方法里面有一个commonInit
函数的调用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?() }
}
}
复制代码
delegate.sessionDidFinishEventsForBackgroundURLSession
闭包的声明,只要后台下载完成就会执行这个闭包backgroundCompletionHandler
, 这是 SessionManger
对外提供的功能。这个闭包就是在AppDelegate
里面handleEventsForBackgroundURLSession
代理中保存的闭包AppDelegate
的handleEventsForBackgroundURLSession
方法里,把回调闭包completionHandler
传给了 SessionManager
的 backgroundCompletionHandler
保存下来。SessionDelegate
的 urlSessionDidFinishEvents
代理会调用,而后执行 sessionDidFinishEventsForBackgroundURLSession
闭包sessionDidFinishEventsForBackgroundURLSession
闭包里面会在主线程执行SessionManager
的 backgroundCompletionHandler
闭包,这个闭包就是 AppDelegate
的 completionHandler
闭包。Alamofire
是对URLSession
进行封装,因此这两种方式进行后台下载,原理是同样的。只是 Alamofire
使用更加简洁方便,依赖下沉,网络层下沉。在本身写sdk或者项目的时候也能够参照这种设计思想去实现。app
有问题或者建议和意见,欢迎你们评论或者私信。 喜欢的朋友能够点下关注和喜欢,后续会持续更新文章。框架