上一篇文章提到了后台下载,下面看看在Alamofire中是如何处理后台下载的。首先使用原生写法来实现一个后台下载任务,在使用Alamofire来实现,通过对比来看看Alamofire的优势。

数据源地址:http://testapi.onapp.top/public/videos/video.mp4

一、URLSession后台下载

首先需要创建会话并设置会话参数:

//1、配置请求参数
let configuration = URLSessionConfiguration.background(withIdentifier: "com.yahibo.background_id")
let session = URLSession.init(configuration: configuration,delegate: self,delegateQueue: OperationQueue.main)
//2、设置数据源
let videoUrl = "http://onapp.yahibo.top/public/videos/video.mp4"
let url = URL.init(string: videoUrl)!
//3、创建一个下载任务,并发起请求
session.downloadTask(with: url).resume()
  • 配置会话为background模式,开启后台下载功能
  • 创建下载任务并执行resume启动任务
  • 会话初始化设置代理后,任务回调只走代理方法,不会通过闭包进行数据回调,如果使用闭包回传也会报错提示
session.downloadTask(with: url) { (url, response, error) inprint(url)print(response)print(error)
}.resume()
错误信息:Completion handler blocks are not supported in background sessions. Use a delegate instead.

在后台会话中不支持block块回调数据,要求使用代理,因此在后台下载中,我们直接使用代理方法来处理数据。代理方法如下:

extension Alamofire2Controller: URLSessionDownloadDelegate{//1、下载进度func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {print("下载进度:\(Double(totalBytesWritten)/Double(totalBytesExpectedToWrite))")}//2、下载完成func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {let locationPath = location.pathprint("下载完成:\(location.path)")//存储到用户目录let documents = NSHomeDirectory() + "/Documents/my.mp4"print("存储位置:\(documents)")//复制视频到目标地址let fileManager = FileManager.defaulttry!fileManager.moveItem(atPath: locationPath, toPath: documents)}
}

实现了对下载任务进度的监听,下载任务完成的监听,在文件下载完成时首先会保存在沙盒中tmp文件下,该文件只存储临时数据,使用完后会自动清理,因此需要将tmp中下载的文件复制到Documents文件夹中存储。

通过打印的路径查看文件下载情况,以上操作实际并没有真正完成后台下载,应用返回后台,下载任务就已停止,进入前台才能看到下载完成,界面不能够及时更新。

下载进度:0.3653140762324527
下载进度:0.4018703091059228
2019-08-19 15:23:14.237923+0800 AlamofireDemo[849:9949] An error occurred on the xpc connection requesting pending callbacks for the background session: Error Domain=NSCocoaErrorDomain Code=4097 "connection to service named com.apple.nsurlsessiond" UserInfo={NSDebugDescription=connection to service named com.apple.nsurlsessiond}
下载完成:/Users/hibo/Library/Developer/CoreSimulator/Devices/404EDFDD-735E-454B-A576-70268D8A17C0/data/Containers/Data/Application/E3175312-D6B8-4576-9B84-4EBD7751A4C0/Library/Caches/com.apple.nsurlsessiond/Downloads/com.yahibo.background_id/CFNetworkDownload_eo4RMO.tmp
存储位置:/Users/hibo/Library/Developer/CoreSimulator/Devices/404EDFDD-735E-454B-A576-70268D8A17C0/data/Containers/Data/Application/E3175312-D6B8-4576-9B84-4EBD7751A4C0/Documents/20190819152314.mp4

上篇文章有提到,苹果官方要求在进行后台任务下载时需要实现两个代理方法,来及时通知系统更新界面。

1、在AppDelegate中实现

var backgroundCompletionHandler: (()->Void)? = nil
//设置此处开启后台下载权限
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {self.backgroundCompletionHandler = completionHandler
}
  • 开启后台下载权限,实现代理方法即为开通

2、在上面Alamofire2Controller扩展中实现代理方法

//后台任务下载回调
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {print("后台任务下载回来")DispatchQueue.main.async {guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundCompletionHandler else { return }backgroundHandle()}
}
  • 后台任务完成会调用该方法,在该方法内部调用AppDelegate中的闭包,通知系统更新界面,否则会出现掉帧

添加以上方法再次运行下载,退出前台,等待几秒钟能够看到在控制台是有后台下载完成回调打印的,在该情况下,我们再次进入前台,我们的页面实际上已经被更新了。至此我们就完成了一个后台下载的功能。

总结:后台下载任务需要实现四个代理方法

控制器:

  • URLSessionDownloadTask:获取下载进度
  • didFinishDownloadingTo:下载完成处理下载文件
  • urlSessionDidFinishEvents:后台下载完成调用,提示系统及时更新界面,执行Application中的闭包函数

Application:

  • backgroundCompletionHandler:后台下载完成接收通知消息的闭包

从多年的开发经验来看(太装了?),以上这种实现方式其实不是理想结果,功能代码分散。下面就看一下Alamofire是如何实现的。

二、Alamofire后台下载

Alamofire.request(url,method: .post,parameters: ["page":"1","size":"20"]).responseJSON {(response) inswitch response.result{case .success(let json):print("json:\(json)")breakcase .failure(let error):print("error:\(error)")break}
}

在以上代码中,Alamofire可以直接通过request发送请求,同样在框架中也存在download方法来完成下载任务。查看官方文档。

//下载文件
Alamofire.download(url, to: { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) inlet documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]let fileURL = documentsURL.appendingPathComponent("\(self.currentDateStr()).mp4")return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
})
.downloadProgress { (progress) inprint(progress)
}.response(queue: DispatchQueue.global(qos: .utility), completionHandler: { (response) inprint("完成下载:\(response)")
})
  • DownloadRequest.DownloadOptions:设置下载文件的存储地
  • downloadProgress:获取下载进度

以上虽然可以下载我们需要的文件,但是不能在后台下载。首先官方指出:

The Alamofire.download APIs should also be used if you need to download data while your app is in the background. For more information, please see the Session Manager Configurations section.

需要我们手动配置会话为background模式,而在以上使用的download中实际上使用的是default模式,并不能支持后台下载。如下代码:

public static let `default`: SessionManager = {let configuration = URLSessionConfiguration.defaultconfiguration.httpAdditionalHeaders = SessionManager.defaultHTTPHeadersreturn SessionManager(configuration: configuration)
}()

通过官方文档和源码的查看,实际上我们只需要重新设置会话的配置信息就可以了。

修改会话模式

let configuration = URLSessionConfiguration.background(withIdentifier:"com.yahibo.background_id")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
sessionManager = SessionManager(configuration: configuration)

以上sessionManager需要设置为一个单例对象,以便于在后台下载模式中接收Appdelegate的代理闭包函数,通过闭包通知系统及时更新界面。代码如下:

struct BackgroundManager {static let shared = BackgroundManager()let manager: SessionManager = {let configuration = URLSessionConfiguration.background(withIdentifier:"com.yahibo.background_id")configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeadersreturn SessionManager(configuration: configuration)}()
}

下面就开始实现下载功能:

BackgroundManager.shared.manager.download(url) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) inlet documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]let fileURL = documentsURL.appendingPathComponent("\(self.currentDateStr()).mp4")return (fileURL, [.removePreviousFile, .createIntermediateDirectories])}.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { (progress) inprint(progress)}.response(queue: DispatchQueue.global(qos: .utility), completionHandler: { (response) inprint("完成下载:\(response)")})
  • 同上直接调用download方法来下载,并存储数据

应苹果要求我们还需要调用handleEventsForBackgroundURLSession中的的代码块,通知系统及时更新界面,在SessionManager中如何做连接呢。代码如下:

//设置此处开启后台下载权限
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {BackgroundManager.shared.manager.backgroundCompletionHandler = completionHandler
}
  • SessionManager中已经备好了需要的backgroundCompletionHandler代码块声明,以便接收闭包,调用闭包

简单几步就实现了我们想要的后台下载功能了,编码简洁,逻辑清晰。这里我们只在Application中实现了开启后台下载权限的代理,但并没有在控制器中设置delegate和实现urlSessionDidFinishEvents代理方法,这里不难猜测URLSessionDownloadTask、didFinishDownloadingTo、urlSessionDidFinishEvents代理方法应该是在SessionManager中实现,统一管理再以闭包的形式回传到当前界面。下面就看一下SessionManager是不是这么实现的。

三、SessionManager源码探索

首先顺着SessionManager的创建找到类中的初始化方法:

public init(configuration: URLSessionConfiguration = URLSessionConfiguration.default,delegate: SessionDelegate = SessionDelegate(),serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{self.delegate = delegateself.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}

初始化有三个初始参数,并设有缺省值,该方法返回一个新的SessionManager对象。在上面后台下载中我们只配置了configuration参数,设置为了后台下载模式。上面也提到了,在SessionManager中应该是有我们的后台下载相关的代理实现,在该函数中看到初始化了一个SessionDelegate对象,并将URLSession的代理实现指向了SessionDelegate对象,不难猜出URLSession相关的代理方法应该都在SessionDelegate类中实现。

SessionDelegate

SessionDelegate.swift中,SessionDelegate继承自NSObject,声明了所有与URLSession代理相关连的闭包函数,用来向界面回传代理事件产生的结果。

在扩展方法中实现了以下几个代理的方法:

URLSessionDelegate
URLSessionTaskDelegate
URLSessionDataDelegate
URLSessionDownloadDelegate
URLSessionStreamDelegate

下面就看一下下载相关的代理方法内部实现了哪些功能。代码如下:

extension SessionDelegate: URLSessionDownloadDelegate {open func urlSession(_ session: URLSession,downloadTask: URLSessionDownloadTask,didFinishDownloadingTo location: URL){if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {delegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)}}open func urlSession(_ session: URLSession,downloadTask: URLSessionDownloadTask,didWriteData bytesWritten: Int64,totalBytesWritten: Int64,totalBytesExpectedToWrite: Int64){if let downloadTaskDidWriteData = downloadTaskDidWriteData {downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {delegate.urlSession(session,downloadTask: downloadTask,didWriteData: bytesWritten,totalBytesWritten: totalBytesWritten,totalBytesExpectedToWrite: totalBytesExpectedToWrite)}}open func urlSession(_ session: URLSession,downloadTask: URLSessionDownloadTask,didResumeAtOffset fileOffset: Int64,expectedTotalBytes: Int64){if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {delegate.urlSession(session,downloadTask: downloadTask,didResumeAtOffset: fileOffset,expectedTotalBytes: expectedTotalBytes)}}
}

以上三个方法用来监控下载进度,及下载是否完成,在回调内部通过闭包回调代理事件到主界面。该文件中实现了上面提到的代理的所有方法,通过声明的闭包向外界传值,在外部只需要调用闭包即可。这里和外界桥接的闭包函数返回一个self,因此能够以链式的形式,来获取代理传来的数据。如下:

open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {downloadDelegate.progressHandler = (closure, queue)return self
}
  • 桥接界面与内部SessionDelegate扩展代理,完成下载进度的监听

其他桥接方法省略……

针对后台下载找到了继承自URLSessionDelegate的扩展:

extension SessionDelegate: URLSessionDelegate {open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {sessionDidFinishEventsForBackgroundURLSession?(session)}
}

后台下载完成,会执行该方法,在该方法中,调用了外界实现的闭包,此闭包实现在SessionManager中,如下:

private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {session.serverTrustPolicyManager = serverTrustPolicyManagerdelegate.sessionManager = selfdelegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session inguard let strongSelf = self else { return }DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }}
}
  • SessionDelegate中传入self,此处出现循环引用,这里的delegate.sessionManager使用weak修饰解决
  • 实现delegate中后台下载完成回调闭包,在此处接收后台下载完成消息
  • 在主线程中,调用backgroundCompletionHandler将消息发送至backgroundCompletionHandler的闭包实现

这里应该就清楚了,backgroundCompletionHandlerSessionManager声明的闭包,在Application中获取系统闭包实现,用来与系统通讯,告诉系统在后台及时更新界面。

Alamofire-后台下载相关推荐

  1. iOS 后台下载及管理库

    说起下载第一个想起的就是ASI.一年前接手的新项目是核心功能是视频相关业务,在修改和解决视频下载相关的问题的时候让我体会到了ASI的下载的强大.后来新需求需要视频后台下载,使用NSURLSession ...

  2. 使用 NSURLSession 开发一个支持后台下载和断点续传的下载工具

    NSURLSession 是 iOS 系统提供给我们的原生网络操作库,它提供了网络操作相关的一系列特性支持,比如缓存控制,Cookie管理,HTTP 认证处理等等,是一套整体的网络操作处理解决方案. ...

  3. android后台时不显示,Android后台下载问题

    下载任务显然需要在主线程之外处理. 而从当前执行下载任务的activity按了返回键,然后再次进入后,该activity已经被销毁并且重建了,并且一般情况下,在activity被销毁后,我们应该清理新 ...

  4. linux查看tar进程进度,Linux:wget后台下载/查看后台任务进度

    今天在自己的服务器上使用wget下载一个大文件时,不小心把ssh断开连接了,重新登上去后想查看这个文件的下载进度,现记录一些wget的知识点. 1:后台下载 使用wget -b + url [root ...

  5. ios 后台下载,断点续传总结

    2018年12月05日 16:09:00 weixin_34101784 阅读数:5 https://blog.csdn.net/weixin_34101784/article/details/875 ...

  6. 基于iOS 10封装的下载器(支持存储读取、断点续传、后台下载、杀死APP重启后的断点续传等功能)

    原文 资源来自:http://www.cocoachina.com/ios/20170316/18901.html 概要 在决定自己封装一个下载器前,我本以为没有那么复杂,可在实际开发过程中困难重重, ...

  7. android端向后台传图片,Android前台从后台下载一张图片 以及 Android前台上传一张图片到后台...

    Android 与 服务器(这里我用的是JSP)对于图片的交互(Android --> JSP && JSP --> Android) Android,在写Android项 ...

  8. linux常用技巧(一):后台下载

    linux常用技巧(一):后台下载 "java常见小错误"系列文章推荐: 上一篇:java小技巧(二):JAVA 交集,差集,并集 前文推荐:java常见小错误(一):变量类型自动 ...

  9. wget下载一半断开了继续下载方法及后台下载和查看日志

    wget下载一半断开了继续下载方法 如果你想从网站上下载一个文件: wget https://www.wangchao.info/bak.tar.gz 如果下载一半中断了要继续下载: wget -c ...

  10. Facebook keyhash 获取方法总结(含 通过google play后台下载的 .der证书获取 keyhash)

    由于google play 推荐 发布签名证书(App签名证书)用google play后台生成的,而google play 后台生成我们可以看到的,直接是 签名证书(签名证书可以下载),而无法拿到g ...

最新文章

  1. 大数据基础设施建设需要得到重视 | 记清华大数据“应用·创新”讲座
  2. California Dreaming
  3. iOS10 推送通知 UserNotifications
  4. IPSEC传输模式和隧道模式的区别
  5. python简介怎么写-python爬虫简历怎么写
  6. 装饰器模式与java.io包
  7. java面试题(java基础)
  8. Java成员方法遵循动态绑定机制
  9. 搞科研、学术的朋友注意了,停下手头的活,再忙也要看一下这个!
  10. 【玩转cocos2d-x之三十二】xml的解析
  11. c语言递归函数变量作用域,C语言课程变量的作用域和生存周期、递归.ppt
  12. python自动化办公是什么_python自动化办公?学这些就够用了
  13. 数据库备份与恢复 之四 选择数据库还原方案
  14. mysql5.1 系列 关于用户授权的一个bug
  15. Python基础教程,Python入门教程(非常详细)
  16. android jcenter google 镜像
  17. SpringBoot实现163邮箱发送邮件
  18. 123456789 中间随机添加 “加减符号” 进行运算结果等于100
  19. Openbravo ERP介绍(三)
  20. deprecate node-sass@4.9.0 › request@~2.79.0 request has been deprecated, see https://github.com

热门文章

  1. 第四次作业--何仁喜
  2. windows通信端口初始化失败_报错1011模拟器启动端口失败,请尝试修复系统!
  3. 分布式事务TCC使用手册
  4. linux epel7安装,在CentOS6.x或CentOS7.x上安装EPEL Repo,Extra Packages for Enterprise Linux (EPEL)...
  5. 一加手机 android 8.0,太厚道!一加手机已开始适配Android 8.0
  6. 菜单和工具条---QT
  7. 中国量子计算机“婴儿”诞生
  8. 关于Transitions-Everywhere
  9. 华为OD机试题 - 字符匹配(JavaScript)| 含详细编码过程
  10. Half Life2 Displacement Terrain System