Alamofire 學(xué)習(xí)(三)-Request 上篇

一、簡介

前兩篇《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 是什么

Alamofireswift 寫的一個非常優(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)識一個類 SesssionManagerSesssionManager 就是對外提供的管理者描验,這個管理者具備整個 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)")
}

AppDelegatehandleEventsForBackgroundURLSession 中用單例接收:

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、methodheaders 創(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() }
}

可以看出這里傳遞不同的枚舉值,初始化不同的 taskdelegate圆到。

  • 綁定 taskrequest
    下面的代碼對 taskrequest 進行了綁定怎抛。方便在 SessionDelegate 下發(fā)任務(wù),task 直接檢索芽淡,request 方便直接獲取马绝。
delegate[task] = request

這一篇對 Request 有了大概的了解,下一篇我們繼續(xù)挣菲。

轉(zhuǎn)載請備注原文出處富稻,不得用于商業(yè)傳播——凡幾多

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市白胀,隨后出現(xiàn)的幾起案子椭赋,更是在濱河造成了極大的恐慌,老刑警劉巖或杠,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哪怔,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機认境,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門胚委,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人叉信,你說我怎么就攤上這事亩冬。” “怎么了硼身?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵鉴未,是天一觀的道長。 經(jīng)常有香客問我鸠姨,道長呀潭,這世上最難降的妖魔是什么谤职? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮鬓长,結(jié)果婚禮上核蘸,老公的妹妹穿的比我還像新娘巍糯。我一直安慰自己,他們只是感情好客扎,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布祟峦。 她就那樣靜靜地躺著,像睡著了一般徙鱼。 火紅的嫁衣襯著肌膚如雪宅楞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天袱吆,我揣著相機與錄音厌衙,去河邊找鬼。 笑死绞绒,一個胖子當(dāng)著我的面吹牛婶希,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蓬衡,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼喻杈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了狰晚?” 一聲冷哼從身側(cè)響起筒饰,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎家肯,沒想到半個月后龄砰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年换棚,在試婚紗的時候發(fā)現(xiàn)自己被綠了式镐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡固蚤,死狀恐怖娘汞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情夕玩,我是刑警寧澤你弦,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站燎孟,受9級特大地震影響禽作,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜揩页,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一旷偿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧爆侣,春花似錦萍程、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乎赴,卻和暖如春忍法,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背榕吼。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工缔赠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人友题。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓嗤堰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親度宦。 傳聞我的和親對象是個殘疾皇子踢匣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

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