在開發中,爲了提高用戶體驗經常會把下載大文件等網絡請求放到後臺下載,這樣即便用戶進入了後臺,任務也能繼續進行。那麼這篇文章就來討論下如何使用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
有問題或者建議和意見,歡迎你們評論或者私信。 喜歡的朋友能夠點下關注和喜歡,後續會持續更新文章。框架