Alamofire之SessionManager

1、SessionManager流程分析

在使用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 作一層封裝呢? 閉包

SessionManagerdefault方法中,使用默認的 URLSessionConfiguration,並添加了Alamofire自身的一些參數到配置中。而後初始化 SessionManager

init 函數中,除了參數 configuration外,還有 delegate 參數。而且這個參數的默認值 SessionDelegate() 被傳給 URLSession 做爲其代理。經過查看源碼,SessionDelegate 已經實現了 URLSessionDelegateURLSessionTaskDelegateURLSessionDataDelegateURLSessionDownloadDelegateURLSessionStreamDelegate的代理方法。app

小夥伴都知道,在直接使用 URLSession 作網絡請求時,通常都會將其代理設置爲當前的 ViewController,而這裏將其代理移交給 SessionDelegate()的目的,就是爲了便於對請求做統一處理,不用讓開發者在每次發起網絡請求時,都去處理其回調。async

咱們繼續查看 commonInit 函數的源碼: ide

commonInit 函數中, delegate.sessionManager 被設置爲自身 self,而 self 實際上是持有 delegate 的。那麼這裏會不會形成循環引用呢?答案是不會。由於 delegatesessionManagerweak 屬性。

這裏將 sessionManagerdelegate 是爲了 一、在 delegate 處理回調的時候能夠將消息回傳給 sessionManager。 二、在 delegate 處理回調時,將不屬於自身的業務交給 sessionManager 統一調度處理。 三、減小 delegate 與其餘業務的依賴。函數

小夥伴們還要注意這裏的 sessionDidFinishEventsForBackgroundURLSession閉包 源碼分析

在處理後臺請求時,須要用到。

以上就是 SessionManager 的源碼分析。 SessionManager 初始化完成後,就直接調用 request 方法請求數據了。此處先不贅述。

2、URLSession後臺下載

小夥伴們在項目中可能會遇到後臺下載的狀況,可能不少小夥伴會以爲這個比較難,但其實 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()
    }
}
複製代碼

添加了以上的代碼後,再運行就能夠實現後臺下載了。

3、Alamofire後臺下載

在上一節中,咱們實現了 URLSession 的簡單後臺下載,那麼在 Alamofire 中又該如何實現後臺下載呢?

小夥伴們可能會仿照第一節中進行網絡請求那樣,調用 Alamofire.download 方法,進行下載。運行代碼彷佛也能夠下載,可是卻不能後臺下載。

查看其源碼,Alamofire.download 調用的是 SessionManager.defaultdownload 方法。在第一節中已經分析過了,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 閉包。

而在第一節分析 SessionManagercommonInit 函數時,已經知道會設置 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,可是更多的在記錄後臺下載的實現。也算是對本身平時項目中所用的一種總結吧。如有不足之處,請評論指正。

相關文章
相關標籤/搜索