Alamofire Request流程分析

Alamofire是一個網(wǎng)絡(luò)請求的框架平夜,使用起來非常簡單线脚,幾行代碼就可以實(shí)現(xiàn)網(wǎng)絡(luò)請求的功能那婉。那么它內(nèi)部到底做了些什么呢板甘?讓我們不用再寫一些繁瑣的代碼就能夠?qū)崿F(xiàn)同樣的功能。這邊文章就來分析下Request模塊的具體實(shí)現(xiàn)详炬。

Request整個流程分析

SessionManager.default.request(urlStr, method: .get, parameters: ["name":"xxx"])
    .response { (response) in
        print(response)
}
  • 這是一個非常簡單的請求網(wǎng)絡(luò)代碼盐类,直接進(jìn)入到request方法里面
open func request(
    _ url: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoding: ParameterEncoding = URLEncoding.default,
    headers: HTTPHeaders? = nil)
    -> DataRequest
{
    var originalRequest: URLRequest?

    do {
        originalRequest = try URLRequest(url: url, method: method, headers: headers)
        let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
        return request(encodedURLRequest)
    } catch {
        return request(originalRequest, failedWith: error)
    }
}
  • 首先創(chuàng)建了一個URLRequest對象,然后對請求參數(shù)進(jìn)行編碼呛谜。Alamofire支持的編碼格式有三種:
    • URLEncoding: URL相關(guān)的編碼在跳,有兩種編碼方式:1.直接拼接到URL中,2.通過request的httpBody傳值
    • JSONEncoding 把參數(shù)字典編碼成JSONData后賦值給request的httpBody
    • PropertyListEncoding 把參數(shù)字典編碼成PlistData后賦值給request的httpBody
  • 進(jìn)入到encode方法查看參數(shù)編碼的具體實(shí)現(xiàn)
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()

    guard let parameters = parameters else { return urlRequest }

    if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
        guard let url = urlRequest.url else {
            throw AFError.parameterEncodingFailed(reason: .missingURL)
        }

        if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
            let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
            urlComponents.percentEncodedQuery = percentEncodedQuery
            urlRequest.url = urlComponents.url
        }
    } else {
        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
        }

        urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
    }

    return urlRequest
}
  • 先獲取urlRequest對象隐岛,然后通過請求方法判斷參數(shù)怎么傳遞猫妙。如果是.get, .head, .delete就把參數(shù)拼接到URL后面,否則放到請求體里面聚凹。
  • 因?yàn)槲覀兊恼埱笫峭ㄟ^ASCII編碼的割坠,所以需要對參數(shù)進(jìn)行百分號編碼。進(jìn)入到query方法妒牙。
private func query(_ parameters: [String: Any]) -> String {
    var components: [(String, String)] = []

    for key in parameters.keys.sorted(by: <) {
        let value = parameters[key]!
        components += queryComponents(fromKey: key, value: value)
    }
    return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
  • 先對參數(shù)通過 ASCII 從小到大進(jìn)行排序韭脊。然后循環(huán)遍歷參數(shù)調(diào)用queryComponents方法。
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
    var components: [(String, String)] = []

    if let dictionary = value as? [String: Any] {
        for (nestedKey, value) in dictionary {
            components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
        }
    } else if let array = value as? [Any] {
        for value in array {
            components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
        }
    } else if let value = value as? NSNumber {
        if value.isBool {
            components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue))))
        } else {
            components.append((escape(key), escape("\(value)")))
        }
    } else if let bool = value as? Bool {
        components.append((escape(key), escape(boolEncoding.encode(value: bool))))
    } else {
        components.append((escape(key), escape("\(value)")))
    }

    return components
}
  • 判斷參數(shù)類型分別處理单旁,如果是集合類型沪羔,需要遞歸調(diào)用此方法。
  • keyvalue取出象浑,然后進(jìn)行了百分號編碼蔫饰。并把編碼后的結(jié)果放進(jìn)元組保存,形成參數(shù)對愉豺。然后把元組加入到數(shù)組中篓吁。
  • components.map { "\($0)=\($1)" }.joined(separator: "&")把數(shù)據(jù)中的數(shù)據(jù)通過map映射成($0)=\($1),然后在映射之后的元素之間加入一個分隔符號&進(jìn)行拼接蚪拦。
  • 如果是get,head,delete方法就直接拼接到URL的后面杖剪,如果是POST等方法就是把這些編碼好的參數(shù)對放入請求體中冻押。其中還要加入Content-Type的請求頭。
  • 回到request方法里面盛嘿,調(diào)用了request(encodedURLRequest)這個方法洛巢,跟蹤進(jì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)
    }
}
  • 參數(shù)urlRequestURLRequestConvertible類型的次兆。URLRequestConvertible協(xié)議的目的是對URLRequest進(jìn)行自定義的轉(zhuǎn)換稿茉。在獲得轉(zhuǎn)換后的URLRequest后,需要用URLRequest生成task芥炭,這樣才能發(fā)起網(wǎng)絡(luò)請求漓库。
  • 在上邊的函數(shù)中,用到了DataRequest.Requestable园蝠,Requestable其實(shí)一個結(jié)構(gòu)體渺蒿,他實(shí)現(xiàn)了TaskConvertible協(xié)議,因此彪薛,它能夠用URLRequest生成與之相對應(yīng)的task蘸嘶。在這里是通過內(nèi)部的Requestable結(jié)構(gòu)體來幫助 DataRequest 創(chuàng)建 Task,任務(wù)分層陪汽,架構(gòu)思路更清晰训唱。
  • 綁定 taskrequest , 方便在 SessionDelegate 下發(fā)任務(wù),task 方便獲取request挚冤,request也方便獲取task况增。
  • 執(zhí)行任務(wù) request.resume() 啟動
  • 再來看看DataRequest的初始化
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
    case .download(let originalTask, let task):
        taskDelegate = DownloadTaskDelegate(task: task)
        self.originalTask = originalTask
    case .upload(let originalTask, let task):
        taskDelegate = UploadTaskDelegate(task: task)
        self.originalTask = originalTask
    case .stream(let originalTask, let task):
        taskDelegate = TaskDelegate(task: task)
        self.originalTask = originalTask
    }

    delegate.error = error
    delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
  • Request 初始化的時候利用了枚舉的便利性,構(gòu)造創(chuàng)建训挡,相關(guān)信息保存澳骤。
  • 在初始化的時候看到保存了一個taskDelegate屬性,那么這個taskDelegate是干嘛用的呢澜薄?前面不是已經(jīng)通過SessionDelegate這個類來專門實(shí)現(xiàn)了相關(guān)代理嗎为肮?為什么這里還要有一個 DataTaskDelegate
    • 首先 SessionDelegate 是所有 Session 的代理遵循者肤京,任何的代理都會來到這里響應(yīng)颊艳。但是里面不會實(shí)現(xiàn)具體的繁瑣的任務(wù),它會把任務(wù)下發(fā)到對應(yīng)任務(wù)執(zhí)行者忘分。
    • DataTaskDelegate 就是我們具體繁瑣任務(wù)執(zhí)行者棋枕,這里面所有方法都是由 SessionDelegate 下發(fā)響應(yīng)的。
    • SessionDelegate 是事件總響應(yīng)者妒峦,它會把具體的事務(wù)交給具體的人去執(zhí)行重斑。不能因?yàn)樵?SessionDelegate 能夠拿到所有的響應(yīng),就把所有的代碼都羅列在這里肯骇,那樣會顯得很臃腫很亂窥浪。所以我們根據(jù)不同的需求祖很,響應(yīng)總代理會根據(jù)需求的不同交給專業(yè)的人去做專業(yè)的事。耦合性大大降低漾脂,架構(gòu)的分層更加明顯假颇。
  • 下面舉個例子:
open func urlSession(
    _ session: URLSession,
    downloadTask: URLSessionDownloadTask,
    didFinishDownloadingTo location: URL)
{
    if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {
        downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)
    } else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
        delegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
    }
}
  • 這是task的一個代理方法,當(dāng)代理回調(diào)時符相,會先通過 task 找到相應(yīng)的 Request拆融,然后直接拿出 Request 的屬性delegate來處理相關(guān)事務(wù)蠢琳,SessionDelegate只是做一個中轉(zhuǎn)的作用勾拉,是一個管理者扣甲,負(fù)責(zé)分發(fā)任務(wù)。從這里也就知道前面為什么要讓Requesttask建立一個綁定關(guān)系。

總結(jié)

調(diào)用request發(fā)起請求后:

  1. 首先對參數(shù)進(jìn)行編碼和拼接灶泵。
  2. 通過內(nèi)部的結(jié)構(gòu)體Requestable創(chuàng)建task
  3. 創(chuàng)建request并保存了DataTaskDelegate甜紫。
  4. requesttask進(jìn)行綁定薪贫,方便使用。
  5. 執(zhí)行resume啟動任務(wù)已卸。

整個流程下來非常的清晰佛玄,容易理解,代碼閱讀性高累澡。SessionDelegate負(fù)責(zé)所有Session的響應(yīng)回調(diào)梦抢,然后把任務(wù)分發(fā)給DataTaskDelegate去具體實(shí)現(xiàn)。實(shí)現(xiàn)解耦愧哟,業(yè)務(wù)下沉奥吩,提高代碼的可以性。

有問題或者建議和意見蕊梧,歡迎大家評論或者私信霞赫。
喜歡的朋友可以點(diǎn)下關(guān)注和喜歡,后續(xù)會持續(xù)更新文章肥矢。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末端衰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子甘改,更是在濱河造成了極大的恐慌靴迫,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楼誓,死亡現(xiàn)場離奇詭異玉锌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)疟羹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門主守,熙熙樓的掌柜王于貴愁眉苦臉地迎上來禀倔,“玉大人,你說我怎么就攤上這事参淫【群” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵涎才,是天一觀的道長鞋既。 經(jīng)常有香客問我,道長耍铜,這世上最難降的妖魔是什么邑闺? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮棕兼,結(jié)果婚禮上陡舅,老公的妹妹穿的比我還像新娘。我一直安慰自己伴挚,他們只是感情好靶衍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茎芋,像睡著了一般颅眶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上田弥,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天涛酗,我揣著相機(jī)與錄音,去河邊找鬼皱蹦。 笑死煤杀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沪哺。 我是一名探鬼主播沈自,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辜妓!你這毒婦竟也來了枯途?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤籍滴,失蹤者是張志新(化名)和其女友劉穎酪夷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孽惰,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晚岭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了勋功。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坦报。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡库说,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出片择,到底是詐尸還是另有隱情潜的,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布字管,位于F島的核電站啰挪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嘲叔。R本人自食惡果不足惜亡呵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望借跪。 院中可真熱鬧政己,春花似錦酌壕、人聲如沸掏愁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽果港。三九已至,卻和暖如春糊昙,著一層夾襖步出監(jiān)牢的瞬間辛掠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工释牺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留萝衩,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓没咙,卻偏偏與公主長得像猩谊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子祭刚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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