Alamofire-后台下载

915 阅读5分钟

作为一个精益求精的项目,在牵涉到下载操作时 ,为了提高用户体验,提高下载效率,不可避免的会使用到后台下载,尤其是下载文件较大时。那么我们从原生下载 URLSessionAlamofire 的后台下载封装循序渐进的讲解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,下沉复杂的配置和回调操作,这就是我们使用第三方框架的原因。