前兩篇《Alamofire 學習(一)-網絡基礎知識準備》和《Alamofire 學習(二)-URLSession 知識準備》,我惡補了一下網絡基礎知識和 URLSession 的知識,有須要的朋友能夠去看看。如今終於能夠開始正式學習 Alamofire 了。git
Alamofire 是 swift 寫的一個很是優秀的網絡請求框架,至關於 OC 中的 AFNetWork,並且與 AF 是同一家出的,其實 AFNetwork 的前綴 AF 即是 Alamofire 的縮寫。它本質是基於 URLSession
的封裝,讓咱們網絡請求相關代碼更簡潔易用。github 上截止如今已經 31.7k 顆⭐️了,附上 github 地址 Alamofire。github
- 鏈式的請求/響應方法
- URL / JSON / plist 參數編碼
- 上傳類型支持:文件( File)、數據( Data)、流( Stream)以及 MultipartFormData
- 支持文件下載,下載支持斷點續傳
- 支持使用 NSURLCredential 進行身份驗證
- HTTP 響應驗證
- TLS Certificate and Public Key Pinning
- Progress Closure & NSProgress
咱們先來認識一個類 SesssionManager,SesssionManager 就是對外提供的管理者,這個管理者具有整個 Alamofire 的全部功能。swift
下面是 SesssionManager 初始化部分的源碼:api
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 是默認了 .default
的模式,並將 代理移交,經過建立 SessionDelegate 這個專門處理代理的類來實現 URLSession 的代理。數組
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
複製代碼
上篇講了 URLSession 的後臺下載,如今再來看一下 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)
}()
}
複製代碼
⚠️注意: 一、設爲
.background
模式: 這裏的 configuration 必定要設置爲.background
模式的(默認是**.default
**),否則沒法實現後臺下載。session二、作成單例: 這裏我把 manager 作成了單例,否則在進入後臺就會釋放,同時網絡也就會報錯:
Error Domain=NSURLErrorDomain Code=-999 "cancelled"
,這樣在 AppDelegate 的回調方便接收。app
調用的時候很方便:框架
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("下載回調信息: \(downloadResponse)")
}
.downloadProgress { (progress) in
print("下載進度 : \(progress)")
}
複製代碼
在 AppDelegate 的handleEventsForBackgroundURLSession
中用單例接收:ide
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
MYBackgroundManager.shared.manager.backgroundCompletionHandler = completionHandler
}
複製代碼
Request 是一個父類,它有以下幾個子類:
使用起來很簡單,像下面這樣就實現了一個簡單的 get 請求
SessionManager.default.request(urlString, method: .get, parameters: ["username":"凡幾多"])
.response { (response) in
debugPrint(response)
}
複製代碼
點擊 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)
}
}
複製代碼
能夠看到先是根據傳進來的 url、method 和 headers 建立了一個 URLRequest,而後對 parameters 進行 URLEncoding 編碼,最後返回一個 DataRequest。 這裏咱們詳細看一下 encoding.encode 裏是如何編碼的, 這裏判斷了 method 的類型,
if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
複製代碼
若是是直接拼接到 url 後面的 method 類型(如 get),假設咱們的請求爲 get 請求,則執行下面的代碼:
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
複製代碼
假設咱們的 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"
複製代碼
咱們發現它是把路由部分 urlComponents 進行了百分號編碼:
(urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "")
複製代碼
而後對參數部分 parameters 也進行了百分號編碼:
query(parameters)
複製代碼
咱們點擊 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: "&")
}
複製代碼
一、先把參數根據 ASCII 進行了升序排序並遍歷:
for key in parameters.keys.sorted(by: <) { let value = parameters[key]! 複製代碼
二、把鍵值對經過
queryComponents
函數進行遞歸併進行百分號編碼,而後返回一個元祖,再把元祖添加到了數組 components 中:components += queryComponents(fromKey: key, value: value) 複製代碼
三、把元祖中的第一個元素和第二個元素用 = 鏈接,而後再用 & 符號進行了分割:
return components.map { "\($0)=\($1)" }.joined(separator: "&") 複製代碼
若是是 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")
}
複製代碼
參數則再也不放到請求頭裏了,而是放在 urlRequest.httpBody中,而且進行了 .data 處理:
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
複製代碼
接下來咱們分析一下內部:url -> request -> task 的過程。這個過程當中還創建了 task 以及 request 之間的綁定關係。 繼續上面的源碼分析:
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)
}
}
}
複製代碼
咱們發現 Requestable 實際上是一個結構體,幫助 DataRequest 建立了一個包含 task 的結構體對象,至關於 DataRequest 的一個助理,幫 Requestable 建立了 task。 之因此這樣寫,而不直接把 task 建立寫在 DataRequest 中,是爲了下降耦合性。
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。
delegate[task] = request
複製代碼
這一篇對 Request 有了大概的瞭解,下一篇咱們繼續。
轉載請備註原文出處,不得用於商業傳播——凡幾多