一、簡介
前兩篇《Alamofire 學(xué)習(xí)(一)-網(wǎng)絡(luò)基礎(chǔ)知識準(zhǔn)備》和《Alamofire 學(xué)習(xí)(二)-URLSession 知識準(zhǔn)備》鄙早,我惡補了一下網(wǎng)絡(luò)基礎(chǔ)知識和 URLSession 的知識汪茧,有需要的朋友可以去看看。現(xiàn)在終于可以開始正式學(xué)習(xí) Alamofire 了限番。
1舱污、Alamofire 是什么
Alamofire 是 swift 寫的一個非常優(yōu)秀的網(wǎng)絡(luò)請求框架,相當(dāng)于 OC 中的 AFNetWork弥虐,而且與 AF 是同一家出的扩灯,其實 AFNetwork 的前綴 AF 便是 Alamofire 的縮寫。它本質(zhì)是基于 URLSession
的封裝霜瘪,讓我們網(wǎng)絡(luò)請求相關(guān)代碼更簡潔易用珠插。github 上截止現(xiàn)在已經(jīng) 31.7k 顆??了,附上 github 地址 Alamofire颖对。
2捻撑、Alamofire 功能特性
- 鏈?zhǔn)降恼埱?響應(yīng)方法
- URL / JSON / plist 參數(shù)編碼
- 上傳類型支持:文件( File)、數(shù)據(jù)( Data)、流( Stream)以及 MultipartFormData
- 支持文件下載顾患,下載支持?jǐn)帱c續(xù)傳
- 支持使用 NSURLCredential 進行身份驗證
- HTTP 響應(yīng)驗證
- TLS Certificate and Public Key Pinning
- Progress Closure & NSProgress
二琳拭、SesssionManager
我們先來認(rèn)識一個類 SesssionManager,SesssionManager 就是對外提供的管理者描验,這個管理者具備整個 Alamofire 的所有功能。
1坑鱼、SesssionManager 的初始化
下面是 SesssionManager 初始化部分的源碼:
public init(
configuration: URLSessionConfiguration = URLSessionConfiguration.default,
delegate: SessionDelegate = SessionDelegate(),
serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
self.delegate = delegate
self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
可以看到它初始化了 session膘流,其中 configuration 是默認(rèn)了 .default
的模式,并將 代理移交鲁沥,通過創(chuàng)建 SessionDelegate 這個專門處理代理的類來實現(xiàn) URLSession 的代理呼股。
2、代理完成回調(diào)
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
三画恰、后臺下載
上篇講了 URLSession 的后臺下載彭谁,現(xiàn)在再來看一下 Alamofire 的后臺下載吧。
我先封裝了一個后臺下載管理類的單例 允扇,
MYBackgroundManager:
struct MYBackgroundManager {
static let shared = MYBackgroundManager()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.lgcooci.AlamofireTest.demo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.timeoutIntervalForRequest = 10
configuration.timeoutIntervalForResource = 10
return SessionManager(configuration: configuration)
}()
}
??注意:
1缠局、設(shè)為.background
模式:
這里的 configuration 一定要設(shè)置為.background
模式的(默認(rèn)是.default
),不然無法實現(xiàn)后臺下載考润。2狭园、做成單例:
這里我把 manager 做成了單例,不然在進入后臺就會釋放糊治,同時網(wǎng)絡(luò)也就會報錯:Error Domain=NSURLErrorDomain Code=-999 "cancelled"
唱矛,這樣在 AppDelegate 的回調(diào)方便接收。
調(diào)用的時候很方便:
MYBackgroundManager.shared.manager
.download(self.urlDownloadStr) { (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])
}
.response { (downloadResponse) in
print("下載回調(diào)信息: \(downloadResponse)")
}
.downloadProgress { (progress) in
print("下載進度 : \(progress)")
}
在 AppDelegate 的handleEventsForBackgroundURLSession
中用單例接收:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
MYBackgroundManager.shared.manager.backgroundCompletionHandler = completionHandler
}
四井辜、Request
Request 是一個父類绎谦,它有如下幾個子類:
- DataRequest
- DownloadRequest
- UploadRequest
-
StreamRequest
不同的 request 有不同的作用,各司其職粥脚。
1窃肠、使用
使用起來很簡單,像下面這樣就實現(xiàn)了一個簡單的 get 請求
SessionManager.default.request(urlString, method: .get, parameters: ["username":"凡幾多"])
.response { (response) in
debugPrint(response)
}
2阿逃、源碼解析
點擊 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)
}
}
可以看到先是根據(jù)傳進來的 url、method 和 headers 創(chuàng)建了一個 URLRequest恃锉,然后對 parameters 進行 URLEncoding 編碼搀菩,最后返回一個 DataRequest。
這里我們詳細(xì)看一下 encoding.encode 里是如何編碼的破托,
這里判斷了 method 的類型肪跋,
if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
(1)get 請求
如果是直接拼接到 url 后面的 method 類型(如 get),假設(shè)我們的請求為 get 請求土砂,則執(zhí)行下面的代碼:
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
假設(shè)我們的 get 請求為:
http://www.douban.com/j/app/radio/channels?username="fanjiduo"&&password="123456"
那么它的 urlComponents 是這樣的:
http://www.douban.com/j/app/radio/channels
- scheme : "http"
- host : "www.douban.com"
- path : "/j/app/radio/channels"
我們發(fā)現(xiàn)它是把路由部分 urlComponents 進行了百分號編碼:
(urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "")
然后對參數(shù)部分 parameters 也進行了百分號編碼:
query(parameters)
我們點擊 query 進去看一下它的具體實現(xiàn)
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: "&")
}
1州既、先把參數(shù)根據(jù) ASCII 進行了升序排序并遍歷:
for key in parameters.keys.sorted(by: <) { let value = parameters[key]!
2谜洽、把鍵值對通過
queryComponents
函數(shù)進行遞歸并進行百分號編碼,然后返回一個元祖吴叶,再把元祖添加到了數(shù)組 components 中:components += queryComponents(fromKey: key, value: value)
3阐虚、把元祖中的第一個元素和第二個元素用 = 連接,然后再用 & 符號進行了分割:
return components.map { "\($0)=\($1)" }.joined(separator: "&")
(2)post 請求
如果是 post 請求蚌卤,那么 encoding.encode 又是如何編碼的呢实束?
post 請求會在請求頭中多加一個 Content-Type:
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}
參數(shù)則不再放到請求頭里了,而是放在 urlRequest.httpBody中逊彭,并且進行了 .data 處理:
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
3咸灿、task 和 request 的關(guān)系
接下來我們分析一下內(nèi)部:url -> request -> task 的過程。這個過程中還建立了 task 以及 request 之間的綁定關(guān)系侮叮。
繼續(xù)上面的源碼分析:
return request(encodedURLRequest)
點 request 進去看一下源碼:
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)
}
}
我們看到這行代碼:
let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
點擊 Requestable 進去:
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)
}
}
}
我們發(fā)現(xiàn) Requestable 其實是一個結(jié)構(gòu)體避矢,幫助 DataRequest 創(chuàng)建了一個包含 task 的結(jié)構(gòu)體對象,相當(dāng)于 DataRequest 的一個助理囊榜,幫 Requestable 創(chuàng)建了 task审胸。
之所以這樣寫,而不直接把 task 創(chuàng)建寫在 DataRequest 中锦聊,是為了降低耦合性歹嘹。
-
初始化 request
通過助理 Requestable 得到結(jié)構(gòu)體 originalTask 以后,就可以得到下面的 task孔庭,并且初始化出 request 了:
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
這個初始化方法是寫在父類 Request 里的尺上,
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() }
}
可以看出這里傳遞不同的枚舉值,初始化不同的 task 和delegate圆到。
- 綁定 task 和 request
下面的代碼對 task 和 request 進行了綁定怎抛。方便在 SessionDelegate 下發(fā)任務(wù),task 直接檢索芽淡,request 方便直接獲取马绝。
delegate[task] = request
這一篇對 Request 有了大概的了解,下一篇我們繼續(xù)挣菲。
轉(zhuǎn)載請備注原文出處富稻,不得用于商業(yè)傳播——凡幾多