Alamofire (2)—— 后台下载

😊😊😊Alamofire专题目录,欢迎及时反馈交流 😊😊😊


Alamofire 目录直通车 --- 和谐学习,不急不躁!


这一篇主要讲解后台下载,后台下载对于应用程序来说,是一个非常重要也比较好用的功能。虽然用好后台下载的确能够大大提升用户体验,但是又很多时候我们也会遇到很多坑点以及疑惑点。其中会通过 URLSessionAlamofire 两种形式分别展开讨论,对比学习才能更能体会 Alamofire 的设计思维。Alamofire持续更新中,希望大家希望!

一、URLSession处理后台下载

URLSession在后台处理方面还是比较简单的。

// 1:初始化一个background的模式的configuration
let configuration = URLSessionConfiguration.background(withIdentifier: self.createID())
// 2:通过configuration初始化网络下载会话
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
// 3:session创建downloadTask任务-resume启动
session.downloadTask(with: url).resume()
  • 初始化一个 background 的模式的 configurationconfiguration 三种模式 ,只有background 的模式才能进行后台下载。
  • 通过configuration初始化网络下载会话 session,设置相关代理,回调数据信号响应。
  • session创建downloadTask任务-resume启动 (默认状态:suspend)
  • 接下来依赖苹果封装的网络处理,发起连接 - 发送相关请求 - 回调代理响应
//MARK: - session代理
extension ViewController:URLSessionDownloadDelegate{
    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")
    }
}
  • 实现了 URLSessionDownloadDelegatedidFinishDownloadingTo代理,实现下载完成转移临时文件里的数据到相应沙盒保存
  • 通过urlSession(_ session: downloadTask:didWriteData bytesWritten: totalBytesWritten: totalBytesExpectedToWrite: ) 的代理监听下载进度
  • 这里也是因为 http的分片传输 才导致的进度有段的感觉,其实证明内部也是对这个代理方法不断调用,才能进度回调!

这里实现了下载功能,但是对于我们需要的后台下载还差一段

Applications using an NSURLSession with a background configuration may be launched or resumed in the background in order to handle the completion of tasks in that session, or to handle authentication. This method will be called with the identifier of the session needing attention. Once a session has been created from a configuration object with that identifier, the session's delegate will begin receiving callbacks. If such a session has already been created (if the app is being resumed, for instance), then the delegate will start receiving callbacks without any action by the application. You should call the completionHandler as soon as you're finished handling the callbacks.

苹果爸爸总是能在合适时间给你优秀的建议,阅读文档的能力决定你是否能够在这个时代站稳自己的脚尖

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    //用于保存后台下载的completionHandler
    var backgroundSessionCompletionHandler: (() -> Void)?
    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        self.backgroundSessionCompletionHandler = completionHandler
    }
}
  • 实现handleEventsForBackgroundURLSession就可以完美后台下载
  • 告诉代理与 URLSession 相关的事件正在等待处理。
  • 应用程序在所有与 URLSession对象 关联的后台传输完成后调用此方法,无论传输成功完成还是导致错误。如果一个或多个传输需要认证,应用程序也会调用这个方法。
  • 使用此方法可以重新连接任何 URLSession 并更新应用程序的用户界面。例如,您可以使用此方法更新进度指示器或将新内容合并到视图中。在处理事件之后,在 completionHandler 参数中执行 block,这样应用程序就可以获取用户界面的刷新。
  • 我们通过handleEventsForBackgroundURLSession保存相应的回调,这也是非常必要的!告诉系统后台下载回来及时刷新屏幕

urlSessionDidFinishEvents的代理实现调用

func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    print("后台任务下载回来")
    DispatchQueue.main.async {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
        backgroundHandle()
    }
}
  • 拿到UIApplication.shared.delegate的回调函数执行
  • 注意线程切换主线程,毕竟刷新界面

那么如果不实现这个代理里面的回调函数的执行,那么会发生什么呢

  • 后台下载的能力是不会影响的
  • 但是会爆出非常验证界面刷新卡顿,影响用户体验
  • 同时打印台会爆出警告
Warning: Application delegate received call to -
application:handleEventsForBackgroundURLSession:completionHandler: 
but the completion handler was never called.

二、Alamofire后台下载

Alamofire框架还是比较有感觉的,这个节奏也是函数式回调,还支持链式请求和响应!事务逻辑非常清晰,还有代码可读性也是非常简洁

LGBackgroundManger.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)")
}
  • 这里封装了一个单利LGBackgroundManger的后台下载管理类,调用manger的手法也是非常直接。
  • 封装的思想再也不需要去处理恶心的代理事件
struct LGBackgroundManger {    
    static let shared = LGBackgroundManger()

    let manager: SessionManager = {
        let configuration = URLSessionConfiguration.background(withIdentifier: "com.lgcooci.AlamofireTest.demo")
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
        configuration.timeoutIntervalForRequest = 10
        configuration.timeoutIntervalForResource = 10
        configuration.sharedContainerIdentifier = "group.com.lgcooci.AlamofireTest"
        return SessionManager(configuration: configuration)
    }()
}

可能很多同学都在质疑为什么要做成单利,URLSession的时候不是挺好的?

  • 如果你是 SessionManager.defalut 显然是不可以的!毕竟要求后台下载,那么我们的会话 session 的配置 URLSessionConfiguration 是要求 background模式的
  • 如果你配置出来不做成单利,或者不被持有!在进入后台就会释放,网络也就会报错:Error Domain=NSURLErrorDomain Code=-999 "cancelled"
  • 应用层与网络层也可以达到分离。
  • 能够帮助在AppDelegate 的回调方便直接接收
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    LGBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}

三、SessionManger流程分析

一篇优秀的博客,毕竟还要跟大家交代这样清晰的代码的背后流程

1、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)
}
  • 初始化了session,其中configurationdefault的模式,设置了一些基本的 SessionManager.defaultHTTPHeaders 请求头信息
  • 代理移交,通过创建 SessionDelegate 这个专门处理代理的类来实现 URLSession的代理
2、代理完成回调

SessionDelegate 是一个非常重要的类,集合所有的代理

  • URLSessionDelegate
  • URLSessionTaskDelegate
  • URLSessionDataDelegate
  • URLSessionDownloadDelegate
  • URLSessionStreamDelegate

这里我们根据需求来到 urlSessionDidFinishEvents 的代理

open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    sessionDidFinishEventsForBackgroundURLSession?(session)
}
  • 这里执行了 sessionDidFinishEventsForBackgroundURLSession 闭包的执行,那么这个闭包在什么时候申明的呢?
  • 如果你足够聪明,这里你应该是能够想到的,SessionDelegate只是处理代理的专门类,但不是逻辑数据的处理类,按照封装设计的常规思路必将交给管理者类来下发

在我们的 SessionManger 里面的初始化的时候,有一个方法commonInit

delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
    guard let strongSelf = self else { return }
    DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
}
  • 这里就是代理的delegate.sessionDidFinishEventsForBackgroundURLSession闭包的声明
  • 只要后台下载完成就会来到这个闭包内部
  • 回调了主线程,调用了 backgroundCompletionHandler , 这也是 SessionManger 对外提供的功能!聪明的你应该知道知道了我在application的操作的本质了!

3、流程总结

  • 首先在 AppDelegatehandleEventsForBackgroundURLSession方法里,把回调闭包传给了 SessionManagerbackgroundCompletionHandler
  • 在下载完成回来的时候 SessionDelegateurlSessionDidFinishEvents代理的调用 -> sessionDidFinishEventsForBackgroundURLSession 调用
  • 然后sessionDidFinishEventsForBackgroundURLSession 执行 -> SessionManagerbackgroundCompletionHandler的执行
  • 最后导致 AppDelegatecompletionHandler 的调用

无论你是使用 URLSession 的方式,还是 Alamofire 进行后台下载,但是原理还是一样的,只是 Alamofire 使用更加达到依赖下沉,网络层下沉,使用更简洁,这也是很多时候我们需要第三方框架的原因。这一篇你估计已经感受到了 Alamofire 的舒服,那么如果你喜欢的话,麻烦点心,关注一下。我会持续更新一个 Alamofire 的系列专题,谢谢!

就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345