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)用此方法。
- 把
key
和value
取出象浑,然后進(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ù)
urlRequest
是URLRequestConvertible
類型的次兆。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)思路更清晰训唱。 - 綁定
task
和request
, 方便在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ù)。從這里也就知道前面為什么要讓Request
和task
建立一個綁定關(guān)系。
總結(jié)
調(diào)用request
發(fā)起請求后:
- 首先對參數(shù)進(jìn)行編碼和拼接灶泵。
- 通過內(nèi)部的結(jié)構(gòu)體
Requestable
創(chuàng)建task
。 - 創(chuàng)建
request
并保存了DataTaskDelegate
甜紫。 - 對
request
和task
進(jìn)行綁定薪贫,方便使用。 - 執(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ù)更新文章肥矢。