在使用Alamofire發起網絡請求時,咱們通常會使用 request
方法請求一個網絡地址。以下所示:swift
let urlBD = "https://www.baidu.com"
Alamofire.request(urlBD).response { (response) in
print(response)
}
複製代碼
在上面👆的代碼中,咱們請求了百度的地址。api
咱們來查看一下 request
的內部實現是什麼樣子的呢? 網絡
request
有多個參數,除了必傳的
url
外都有默認值。而後返回了一個
SessionManager.default.request
。
小夥伴們應該知道,在iOS進行網絡請求的時候,是調用的 URLSession.dataTask
,以下:session
let session = URLSession.init(configuration: .default)
let task = session.dataTask(with: URL(string: urlBD)!) { (data, response, error) in
print(response)
}
task.resume()
複製代碼
那麼爲何在Alamofire中不直接使用 URLSession
,而要使用 SessionManager
作一層封裝呢? 閉包
SessionManager
的
default
方法中,使用默認的
URLSessionConfiguration
,並添加了Alamofire自身的一些參數到配置中。而後初始化
SessionManager
。
在 init
函數中,除了參數 configuration
外,還有 delegate
參數。而且這個參數的默認值 SessionDelegate()
被傳給 URLSession
做爲其代理。經過查看源碼,SessionDelegate
已經實現了 URLSessionDelegate
、URLSessionTaskDelegate
、URLSessionDataDelegate
、URLSessionDownloadDelegate
、URLSessionStreamDelegate
的代理方法。app
小夥伴都知道,在直接使用
URLSession
作網絡請求時,通常都會將其代理設置爲當前的 ViewController,而這裏將其代理移交給SessionDelegate()
的目的,就是爲了便於對請求做統一處理,不用讓開發者在每次發起網絡請求時,都去處理其回調。async
咱們繼續查看 commonInit
函數的源碼: ide
commonInit
函數中,
delegate.sessionManager
被設置爲自身
self
,而
self
實際上是持有
delegate
的。那麼這裏會不會形成循環引用呢?答案是不會。由於
delegate
的
sessionManager
是
weak
屬性。
這裏將
sessionManager
給delegate
是爲了 一、在delegate
處理回調的時候能夠將消息回傳給sessionManager
。 二、在delegate
處理回調時,將不屬於自身的業務交給sessionManager
統一調度處理。 三、減小delegate
與其餘業務的依賴。函數
小夥伴們還要注意這裏的 sessionDidFinishEventsForBackgroundURLSession
閉包 源碼分析
以上就是 SessionManager 的源碼分析。 SessionManager
初始化完成後,就直接調用 request
方法請求數據了。此處先不贅述。
小夥伴們在項目中可能會遇到後臺下載的狀況,可能不少小夥伴會以爲這個比較難,但其實 URLSession
的後臺處理仍是很簡單的,下面咱們來簡單實現一下。
// 初始化一個background的模式的configuration
let configuration = URLSessionConfiguration.background(withIdentifier: "BOBackground")
// 經過configuration初始化網絡下載會話
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
// 建立下載URL,下載QQ的安裝包
let downloadUrl = URL(string: "https://dldir1.qq.com/qqfile/QQforMac/QQ_V6.5.5.dmg")!
// session建立downloadTask任務
let task = session.downloadTask(with: downloadUrl)
// 啓動下載任務
task.resume()
複製代碼
上面的代碼就是一個簡單的下載QQ安裝包的下載任務。
background
模式的 configuration
。downloadTask
建立後,默認是掛起狀態,因此須要調用 resume()
啓動下載任務。再實現代理方法。
extension ViewController: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// 下載完成 - 開始沙盒遷移
print("下載完成 - \(location)")
let locationPath = location.path
//拷貝到用戶目錄
let documnets = NSHomeDirectory() + "/Documents/" + "QQ" + ".dmg"
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)\n 已下載: \(totalBytesWritten)\n 預計需下載: \(totalBytesExpectedToWrite)")
print("下載進度: \(Double(totalBytesWritten)/Double(totalBytesExpectedToWrite))\n")
}
}
複製代碼
實現上面兩個協議方法來顯示下載進度,以及將文件下載到指定路徑。
咱們就實現了後臺下載了嗎?咱們是實際運行一下。發現確實能夠下載了,可是當APP進入後臺後,下載就中止了,並無實現後臺下載。
通過一番資料查找(蘋果官方文檔),告訴咱們,還有最後一個步沒有實現。
首先咱們須要在 AppDelegate
中
UIApplicationDelegate
協議方法獲取到
completionHandler
閉包保存起來。
在 ViewController 中,經過實現協議方法 urlSessionDidFinishEvents(forBackgroundURLSession:)
,執行這個閉包
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("後臺任務下載回來")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let backgroundHandle = appDelegate.backgroundCompletionHandler else { return }
backgroundHandle()
}
}
複製代碼
添加了以上的代碼後,再運行就能夠實現後臺下載了。
在上一節中,咱們實現了 URLSession 的簡單後臺下載,那麼在 Alamofire 中又該如何實現後臺下載呢?
小夥伴們可能會仿照第一節中進行網絡請求那樣,調用 Alamofire.download
方法,進行下載。運行代碼彷佛也能夠下載,可是卻不能後臺下載。
查看其源碼,Alamofire.download
調用的是 SessionManager.default
的 download
方法。在第一節中已經分析過了,SessionManager.default
使用的是 URLSessionConfiguration.default
默認配置。可是在 URLSession
的後臺下載應該使用 URLSessionConfiguration.background
。
因此咱們應該自定義一個 SessionManager
,而且使用 URLSessionConfiguration.background
初始化 SessionManager
。
struct BOBackgroundManager {
static let `default` = BOBackgroundManager()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "BOBackground")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
}
複製代碼
定義 BOBackgroundManager 單例來處理後臺下載。
let downloadUrl = "https://dldir1.qq.com/qqfile/QQforMac/QQ_V6.5.5.dmg"
BOBackgroundManager.default.manager
.download(downloadUrl, to: { (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])
})
.downloadProgress { (progress) in
print("下載進度:\(progress)")
}
複製代碼
而後使用 BOBackgroundManager.default.manager
開啓下載,如上代碼。
可是若是須要後臺下載,還須要處理在 AppDelegate 中處理 completionHandler。
由於 SessionDelegate
已經實現了 func urlSessionDidFinishEvents(forBackgroundURLSession:)
方法。
SessionDelegate.sessionDidFinishEventsForBackgroundURLSession
閉包。
而在第一節分析 SessionManager
的 commonInit
函數時,已經知道會設置 SessionDelegate.sessionDidFinishEventsForBackgroundURLSession
閉包。並在閉包內部執行 SessionManager.backgroundCompletionHandler
閉包。因此咱們只須要在 AppDelegate 中將獲取到的 completionHandler
閉包,保存在 SessionManager.backgroundCompletionHandler
便可。
// 經過該方法獲取到completionHandler
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
BOBackgroundManager.default.manager.backgroundCompletionHandler = {
print("下載完成了")
completionHandler()
}
}
複製代碼
爲了方便調試,因此添加 print
函數。
通過以上的設置,便可使用 Alamofire
實現後臺下載。
本篇名字雖然是Alamofire之SessionManager,可是更多的在記錄後臺下載的實現。也算是對本身平時項目中所用的一種總結吧。如有不足之處,請評論指正。