Alamofire-從一個簡單的請求深入源代碼(3)

Alamofire.request

request 函數(shù)簽名

request 函數(shù)實現(xiàn)

SessionManager.default

SessionManager.default.request

之前我們寫的 Alamofire.request 最終就是調(diào)用到這里來了, 現(xiàn)在終于可以看看這里到底做了些什么了

open func request(
    _ url: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoding: ParameterEncoding = URLEncoding.default,
    headers: HTTPHeaders? = nil)
    -> DataRequest
{
    var originalRequest: URLRequest?

    do {
        // 根據(jù) url , header 生成請求
        originalRequest = try URLRequest(url: url, method: method, headers: headers)
        // 編碼進(jìn)去參數(shù)
        let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
        return request(encodedURLRequest)
    } catch {
        return request(originalRequest, failedWith: error)
    }
}

可以看到, 這里調(diào)用了我們之前介紹過的 ParameterEncoding 中的 encoding 方法編碼參數(shù).
這里利用 url 等相關(guān)信息, 生成了一個 URLRequest 對象, 再利用它去請求數(shù)據(jù)

另一個 request 函數(shù)

open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
  ...
}

拋開函數(shù)體先不談, 這個函數(shù)的參數(shù)跟 URLConvertible 也是一樣, 是一個協(xié)議. 想必你已經(jīng)猜出來了. 這個協(xié)議一定是為了將對象轉(zhuǎn)換成URLRequest
接下來, 看看函數(shù)體
敲黑板, 重點(diǎn)來了

open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
    var originalRequest: URLRequest?
    do {
        originalRequest = try urlRequest.asURLRequest()
        let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
        let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
        let request = DataRequest(session: session, requestTask: .data(originalTask, task))
        delegate[task] = request
        if startRequestsImmediately { request.resume() }
        return request
    } catch {
        return request(originalRequest, failedWith: error)
    }
}

這里出現(xiàn)了很多新的類型, 先一步一步分析

originalRequest = try urlRequest.asURLRequest()

這一步, 只是為了獲取 URLRequest 對象而已, 當(dāng)然, 有可能會出錯, 所以有 try 語句.
接下來這一句

let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)

RequestableDataRequest 中的的一個內(nèi)部類型, DataRequest 我們先放一放, 先看看Requestable

Requestable

這個類型是一個結(jié)構(gòu)體, 代碼很少, 也很簡單

struct Requestable: TaskConvertible {
    let urlRequest: URLRequest

    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
        do {
            let urlRequest = try self.urlRequest.adapt(using: adapter)
            return queue.sync { session.dataTask(with: urlRequest) }
        } catch {
            throw AdaptError(error: error)
        }
    }
}

這個結(jié)構(gòu)體實現(xiàn)了一個 TaskConvertible 協(xié)議, 想必你也猜出來了, 這個協(xié)議有一個生成 URLSessionTask 的方法.
結(jié)構(gòu)體中, 除了那個方法之外, 還有一個 urlRequest 屬性, 用于保存對應(yīng)的 URLRequest, 之前的調(diào)用的構(gòu)造函數(shù)也是為了初始化這個屬性.
task 函數(shù)中, 除了必要的 session 參數(shù)之外, 還有一個 RequestAdapterDispatchQueue
RequestAdapter 可以在創(chuàng)建URLSessionTask 之前, 修改URLRequest 對象, 這是一個很有用的東西, 你可以拿這個做很多事情, 比如, 加上一個 token, 或是一個授權(quán)碼等等.
RequestAdapter 本身是一個協(xié)議, 只有一個方法, 實現(xiàn)起來也超級容易

public protocol RequestAdapter {
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}

如果你自定義了一個 Adapter, 要如何使用呢?
很簡單, 賦值到 SessionManager 里的 adapter 屬性就好了, 例如

class TokenAdapter: RequestAdapter{
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var urlRequest = urlRequest
        urlRequest.addValue("some token", forHTTPHeaderField: "ACCESS-TOKEN")
        return urlRequest
    }
}
...
// 由于要修改 sessionManager, 所以這里就不能直接使用 request 方法了, 我們需要自己創(chuàng)建一個 sessionManager
let sessionManager = Alamofire.SessionManager.default
// 設(shè)置 adapter
sessionManager.adapter = TokenAdapter()
// 使用這個 sessionManager 發(fā)起請求
sessionManager.request("https://httpbin.org/get").responseString { (response) in
    if let string = response.result.value {
        print("alamofire with adapter", string)
    }
}

而另一個參數(shù), queue, 這里調(diào)用方式是, queue.sync 同步執(zhí)行. 在這里主要作用是保證同一時間只會同時創(chuàng)建一個 URLSessionTask, queue 本身也是一個串行的隊列
可以在 SessionManager 里面看到定義

open class SessionManager {
    ...
    let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)
    ...
}

繼續(xù)回到 reqeust 函數(shù)中, 下一句

let task = try originalTask.task(session: session, adapter: adapter, queue: queue)

這一句, 也就是生成 URLSessionTask, 跟我們寫原聲代碼的這一句是一樣的.

let dataTask = session.dataTask(with: URL(string: "https://httpbin.org/get")!)

接下來繼續(xù)看下一句代碼

let request = DataRequest(session: session, requestTask: .data(originalTask, task))

這里使用了一個 DataRequest 類型.

DataRequest

DataRequest 負(fù)責(zé)發(fā)送請求, 接收響應(yīng), 并在內(nèi)部管理著 URLSessionTask
DataRequest 繼承自 Request, 除了這一個子類之外, 常用的還有下載用的請求 DownloadRequest, 上傳請求 UploadRequest.
父類中有一個內(nèi)部類型RequestTask 用于區(qū)別這幾種不同的請求

enum RequestTask {
    case data(TaskConvertible?, URLSessionTask?)
    case download(TaskConvertible?, URLSessionTask?)
    case upload(TaskConvertible?, URLSessionTask?)
    case stream(TaskConvertible?, URLSessionTask?)
}

而上面調(diào)用的構(gòu)造函數(shù)中, 第二個參數(shù)就是這個枚舉
.data(originalTask, task)則表示我們要初始化的是一個數(shù)據(jù)類型的請求, 來看看這個構(gòu)造函數(shù)

init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
   self.session = session
   switch requestTask {
   case .data(let originalTask, let task):
       taskDelegate = DataTaskDelegate(task: task)
       self.originalTask = originalTask
       ... 其他類型請求
   }
   delegate.error = error
   delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}

可以大致看出來, 首先是根據(jù)請求的類型以及 task 參數(shù)(URLSessionTask 類型) , 生成了一個 DataTaskDelegate,
并將originalTask(TaskConvertible 類型) 保存起來了, 這個是為了后面如果需要, 例如網(wǎng)絡(luò)錯誤, 需要重試, 可以重新生成一個 URLSessionTask
接下來, 如果有錯誤, 將錯誤保存在其中, 并添加了一個操作.
看起來有點(diǎn)復(fù)雜, 我們一點(diǎn)一點(diǎn)分解.

兩個代理對象taskDelegate 與 delegate

其實這兩個都是 SessionManager 里面的屬性, 都是指向的同一個對象, 不過 delegate 使用起來是線程安全的
定義如下

private var taskDelegate: TaskDelegate
private var taskDelegateLock = NSLock()
open internal(set) var delegate: TaskDelegate {
    get {
        taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
        return taskDelegate
    }
    set {
        taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
        taskDelegate = newValue
    }
}

DataTask設(shè)置Delegate

TaskDelegate 類型主要作用之一就是管理與 URLSessionTask 關(guān)聯(lián)的回調(diào).
看到這里, 你可能會感到疑惑, URLSessionTask 并不能設(shè)置回調(diào), 唯一獲取事件回調(diào)的地方只有一個, 就是我們最初設(shè)置的 SessionDelegate(). 但是 SessionDelegate 內(nèi)部將與 URLSessionTask 關(guān)聯(lián)的任務(wù)又重新分發(fā)出來了, 所以, 這里的 TaskDelegate 才能接收到事件

SessionDelegate

SessionDelegate 中實現(xiàn)了所有URLSessionDelegate及子協(xié)議的方法, 如URLSessionTaskDelegate, URLSessionDataDelegate, 并且以閉包的形式暴露出來, 我們這里截取部分代碼略微說明一下

open class SessionDelegate: NSObject {
    open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?

    weak var sessionManager: SessionManager?
    private var requests: [Int: Request] = [:]
    private let lock = NSLock()
    open subscript(task: URLSessionTask) -> Request? {
        get {
            lock.lock() ; defer { lock.unlock() }
            return requests[task.taskIdentifier]
        }
        set {
            lock.lock() ; defer { lock.unlock() }
            requests[task.taskIdentifier] = newValue
        }
    }
}
extension SessionDelegate: URLSessionDataDelegate {
    open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        if let dataTaskDidReceiveData = dataTaskDidReceiveData {
            dataTaskDidReceiveData(session, dataTask, data)
        } else if let delegate = self[dataTask]?.delegate as? DataTaskDelegate {
            delegate.urlSession(session, dataTask: dataTask, didReceive: data)
        }
    }
}

我們以dataTaskDidReceiveData 這個回調(diào)為例.
為了能夠讓 TaskDelegate 能夠接受到事件. 我們需要做以下幾件事

  1. 創(chuàng)建一個 SessionDelegate 用于接收所有事件
  2. 將請求的 URlSessionTaskTaskDelegate 以某種方式綁定起來
  3. 產(chǎn)生事件后, 通過與事件關(guān)聯(lián)的 URlSessionTask 找到 TaskDelegate并執(zhí)行對應(yīng)的函數(shù)

第一步我們已經(jīng)在最開始創(chuàng)建 URLSession 的時候做了.
第二步, 由于我們這里的 TaskDelegate 都是與 Request類一一對應(yīng), 所以, 我們在 SessionDelegate 中通過下標(biāo)寫入的方式就可以把 URlSessionTaskTaskDelegate 綁定起來. 如以下代碼

someSessionDelegate[someUrlSessionTask] = SomeRequest

第三步, 我們可以在 SessionDelegate 中擴(kuò)展URLSessionDataDelegate部分看到.
如果用戶沒有手動的去實現(xiàn)SessionDelegate 的對應(yīng)屬性, 那么就會自動去找對應(yīng)的Request, 然后獲取內(nèi)部的 TaskDelegate, 最后, 調(diào)用 TaskDelegate 中對應(yīng)的方法.

TaskDelegate 除了接收事件外, 還有一個很大的功能. 可以在任務(wù)完成之后, 完成一些任務(wù). 我們繼續(xù)回到Request 類的構(gòu)造函數(shù)中, 有一句這樣一句代碼

delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }

這句代碼就會在任務(wù)請求結(jié)束之后執(zhí)行, 并將請求結(jié)束的實際記錄下來.
我們來看看 TaskDelegate

TaskDelegate
open class TaskDelegate: NSObject {
    open let queue: OperationQueue
    private var _task: URLSessionTask? {
        didSet { reset() }
    }
    init(task: URLSessionTask?) {
        _task = task
        self.queue = {
            let operationQueue = OperationQueue()
            operationQueue.maxConcurrentOperationCount = 1
            operationQueue.isSuspended = true
            operationQueue.qualityOfService = .utility
            return operationQueue
        }()
    }
    ...
}

可以看到, TaskDelegate 在構(gòu)造的時候, 內(nèi)部除了管理
URLSessionTask 之外 還維護(hù)了一個任務(wù)隊列, 一開始是暫停的, 當(dāng)請求結(jié)束時, 就會恢復(fù). 這樣也就可以在請求執(zhí)行完畢時, 執(zhí)行某些邏輯. 除這里的記錄時間外, 還可以做很多事情, 例如將返回結(jié)果轉(zhuǎn)換為一個字符串.
現(xiàn)在我們對 DataRequest 的構(gòu)造過程(其實是父類Request) 有一個理解.
接下來我們繼續(xù)回到 之前的 request 函數(shù)中, 創(chuàng)建好 DataRequest 后, 我們就需要將其與 SessionDelegate 綁定起來

// 有點(diǎn)暈了?這里的 delegate 是 SessionDelegte 類型的
delegate[task] = request

接下來, 我們就開始發(fā)起請求了

if startRequestsImmediately { request.resume() }

由于 startRequestsImmediately 的默認(rèn)值是 true

open class SessionManager {
    ...
    open var startRequestsImmediately: Bool = true
    ...
}

所以我們這里就立即開始發(fā)起請求了.

發(fā)起請求 request.resume()

終于, 我們將請求發(fā)送出去了, 這里調(diào)用的依然是 Request 類中的方法, 我們看一下實現(xiàn)

 open func resume() {
    guard let task = task else { 
        delegate.queue.isSuspended = false
        return
     }
    if startTime == nil {
        startTime = CFAbsoluteTimeGetCurrent() 
    }
    task.resume()
    NotificationCenter.default.post(
        name: Notification.Name.Task.DidResume,
        object: self,
        userInfo: [Notification.Key.Task: task]
    )
}

其實也就是簡單的幾步, 首先, 獲取 URLSessionTask, 記錄開始時間, 發(fā)起請求, 發(fā)送通知.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拳锚,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子沉噩,更是在濱河造成了極大的恐慌晨仑,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胁编,死亡現(xiàn)場離奇詭異馒稍,居然都是意外死亡礁哄,警方通過查閱死者的電腦和手機(jī)构罗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門铜涉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人遂唧,你說我怎么就攤上這事芙代。” “怎么了盖彭?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵纹烹,是天一觀的道長。 經(jīng)常有香客問我召边,道長铺呵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任隧熙,我火速辦了婚禮片挂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贱鼻。我一直安慰自己宴卖,他們只是感情好滋将,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布邻悬。 她就那樣靜靜地躺著,像睡著了一般随闽。 火紅的嫁衣襯著肌膚如雪父丰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音蛾扇,去河邊找鬼攘烛。 笑死,一個胖子當(dāng)著我的面吹牛镀首,可吹牛的內(nèi)容都是我干的坟漱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼更哄,長吁一口氣:“原來是場噩夢啊……” “哼芋齿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起成翩,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤觅捆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后麻敌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栅炒,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年术羔,在試婚紗的時候發(fā)現(xiàn)自己被綠了赢赊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡级历,死狀恐怖域携,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鱼喉,我是刑警寧澤秀鞭,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站扛禽,受9級特大地震影響锋边,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜编曼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一豆巨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掐场,春花似錦往扔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嚷堡,卻和暖如春蝗罗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背串塑。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工沼琉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留桩匪,地道東北人傻昙。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓葛碧,卻偏偏與公主長得像进泼,于是被迫代替她去往敵國和親纤虽。 傳聞我的和親對象是個殘疾皇子乳绕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容