IOS框架使用: Alamofire 5

原創(chuàng):知識(shí)點(diǎn)總結(jié)性文章
創(chuàng)作不易,請(qǐng)珍惜蛛壳,之后會(huì)持續(xù)更新杏瞻,不斷完善
個(gè)人比較喜歡做筆記和寫(xiě)總結(jié),畢竟好記性不如爛筆頭哈哈衙荐,這些文章記錄了我的IOS成長(zhǎng)歷程捞挥,希望能與大家一起進(jìn)步
溫馨提示:由于簡(jiǎn)書(shū)不支持目錄跳轉(zhuǎn),大家可通過(guò)command + F 輸入目錄標(biāo)題后迅速尋找到你所需要的內(nèi)容

目錄

  • 一忧吟、系統(tǒng)提供的原生框架URLSession的使用
    • 1砌函、請(qǐng)求網(wǎng)絡(luò)的流程
    • 2、屬性
    • 3溜族、HTTP
  • 二讹俊、初涉Alamofire
    • 1、發(fā)起請(qǐng)求
    • 2煌抒、HTTP Methods
    • 3仍劈、請(qǐng)求參數(shù)和參數(shù)編碼器
    • 4、HTTP Headers
    • 5摧玫、響應(yīng)驗(yàn)證
    • 6耳奕、響應(yīng)處理
    • 7绑青、身份驗(yàn)證
    • 8诬像、下載文件
    • 9屋群、上傳數(shù)據(jù)到服務(wù)器
    • 10、網(wǎng)絡(luò)可達(dá)性
  • 三坏挠、玩轉(zhuǎn)Alamofire
    • 1芍躏、Session
    • 2、Request
    • 3降狠、Security
  • Demo
  • 參考文獻(xiàn)

一对竣、系統(tǒng)提供的原生框架URLSession的使用

1、請(qǐng)求網(wǎng)絡(luò)的流程

a榜配、請(qǐng)求網(wǎng)絡(luò)的基本方式
  • URLSession.shared提供了一個(gè)共享的單例會(huì)話對(duì)象否纬,它為創(chuàng)建任務(wù)提供了一個(gè)默認(rèn)行為。使用共享會(huì)話僅用幾行代碼就可以將URL的內(nèi)容獲取到蛋褥。
  • dataTask創(chuàng)建一個(gè)網(wǎng)絡(luò)會(huì)話數(shù)據(jù)任務(wù)临燃。
  • 網(wǎng)絡(luò)任務(wù)默認(rèn)是掛起的,調(diào)用resume開(kāi)始進(jìn)行連接請(qǐng)求網(wǎng)絡(luò)
  • 請(qǐng)求成功或者失敗都會(huì)返回結(jié)果閉包烙心,其實(shí)閉包只是一層封裝膜廊,真正來(lái)的是URLSession的代理
  • 在下面的過(guò)程中,我們省略了一個(gè)重要的東西:URLSessionConfiguration
let url = URL(string: "https://www.baidu.com")!
URLSession.shared.dataTask(with: url)
{ (data, response, error) in
    if error == nil
    {
        print("請(qǐng)求網(wǎng)絡(luò)成功:\(String(describing: response))" )
    }
}.resume()

輸出結(jié)果為:

請(qǐng)求網(wǎng)絡(luò)成功:Optional(<NSHTTPURLResponse: 0x600001b1ee20> { URL: https://www.baidu.com/ } { Status Code: 200, Headers {
    "Content-Encoding" =     (
        gzip
    );
    "Content-Length" =     (
        1145
    );
    "Content-Type" =     (
        "text/html"
    );
    Date =     (
        "Thu, 21 Jan 2021 07:15:16 GMT"
    );
    Server =     (
        bfe
    );
} })

b淫茵、區(qū)別Configuration中的default與ephemeral
  • default:默認(rèn)模式爪瓜,通常我們用這種模式就足夠了。default模式下系統(tǒng)會(huì)創(chuàng)建一個(gè)持久化的緩存并在用戶的鑰匙串中存儲(chǔ)證書(shū)
  • ephemeral:系統(tǒng)沒(méi)有進(jìn)行任何持久性存儲(chǔ)匙瘪,所有內(nèi)容的生命周期都與session相同铆铆,當(dāng)session無(wú)效時(shí),所有內(nèi)容自動(dòng)釋放
let defaultConfiguration = URLSessionConfiguration.default
let ephemeralConfiguration = URLSessionConfiguration.ephemeral
print("default 沙盒大小: \(String(describing: defaultConfiguration.urlCache?.diskCapacity))")
print("default 內(nèi)存大小: \(String(describing: defaultConfiguration.urlCache?.memoryCapacity))")
print("ephemeral 沙盒大小: \(String(describing: ephemeralConfiguration.urlCache?.diskCapacity))")
print("ephemeral 內(nèi)存大小: \(String(describing: ephemeralConfiguration.urlCache?.memoryCapacity))")

從輸出結(jié)果中可以看到ephemeral的沙盒大小為0丹喻,而default有一個(gè)沙盒大小算灸,即系統(tǒng)為default提供了一定大小的沙盒來(lái)存儲(chǔ)證書(shū)等內(nèi)容。

default 沙盒大小: Optional(10000000)
default 內(nèi)存大小: Optional(512000)
ephemeral 沙盒大小: Optional(0)
ephemeral 內(nèi)存大小: Optional(512000)

c驻啤、切換到后臺(tái)停止下載問(wèn)題
  • background會(huì)創(chuàng)建一個(gè)可以在后臺(tái)甚至APP已經(jīng)關(guān)閉的時(shí)候仍然傳輸數(shù)據(jù)的會(huì)話
  • background模式與default模式非常相似菲驴,不過(guò)background模式會(huì)用一個(gè)獨(dú)立線程來(lái)進(jìn)行數(shù)據(jù)傳輸
  • background模式可以在程序掛起,退出骑冗,崩潰的情況下赊瞬,在重新啟動(dòng)APP時(shí)繼續(xù)運(yùn)行task
  • 可以利用標(biāo)識(shí)符來(lái)進(jìn)行恢復(fù)task。后臺(tái)Session一定要在創(chuàng)建的時(shí)候賦予一個(gè)唯一的identifier贼涩,這樣在APP下次運(yùn)行的時(shí)候巧涧,能夠根據(jù)identifier來(lái)進(jìn)行相關(guān)的區(qū)分
  • 如果用戶強(qiáng)制關(guān)閉了APP,IOS 系統(tǒng)會(huì)關(guān)閉所有的background Session遥倦,只有當(dāng)用戶下次啟動(dòng)了APP谤绳,數(shù)據(jù)傳輸任務(wù)才會(huì)繼續(xù)執(zhí)行
// 配置Configuration
let backgroundConfiguration = URLSessionConfiguration.background(withIdentifier: createID())

// 創(chuàng)建URLSession
let backgroundURLSession = URLSession.init(configuration: backgroundConfiguration, delegate: self, delegateQueue: OperationQueue.main)

// 開(kāi)始下載
backgroundURLSession.downloadTask(with: downloadUrl).resume()
? 下載完成后進(jìn)行沙盒遷移占锯,拷貝下載完成的文件到用戶目錄(文件名以時(shí)間戳命名)
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
{
    print("下載完成后文件位置:\(location)")
    let originalLocationPath = location.path
    let destinationPath = NSHomeDirectory() + "/Documents/" + currentDateTurnString() + ".mp4"
    print("文件移動(dòng)后的位置:\(destinationPath)")
    let fileManager = FileManager.default
    try! fileManager.moveItem(atPath: originalLocationPath, toPath: destinationPath)
}

輸出結(jié)果為:

下載完成后文件位置:file:///Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/67D3E5B6-989E-4137-9ED0-06F852F31CA3/Library/Caches/com.apple.nsurlsessiond/Downloads/com.xiejiapei.UseAlamofire/CFNetworkDownload_LX9nZT.tmp
文件移動(dòng)后的位置:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/67D3E5B6-989E-4137-9ED0-06F852F31CA3/Documents/20210121145809.mp4
? 計(jì)算下載進(jìn)度
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
{
    print("bytesWritten: \(bytesWritten)\n totalBytesWritten: \(totalBytesWritten)\n totalBytesExpectedToWrite: \(totalBytesExpectedToWrite)")
    
    print("下載進(jìn)度條:\( Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) )")
}

輸出結(jié)果為:

......
bytesWritten: 139246
 totalBytesWritten: 17218551
 totalBytesExpectedToWrite: 17244422
下載進(jìn)度條:0.998499746758691
bytesWritten: 25871
 totalBytesWritten: 17244422
 totalBytesExpectedToWrite: 17244422
下載進(jìn)度條:1.0
? 保存后臺(tái)下載時(shí)的completionHandler,用于開(kāi)啟后臺(tái)下載權(quán)限
var backgroundSessionCompletionHandler: (() -> Void)?

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void)
{
    self.backgroundSessionCompletionHandler = completionHandler
}

如果不這樣做缩筛,當(dāng)用戶將APP切換到后臺(tái)的時(shí)候下載就會(huì)直接中斷消略,但是當(dāng)回到APP的時(shí)候會(huì)繼續(xù)之前的進(jìn)度進(jìn)行下載。

2021-01-21 15:34:12.825608+0800 UseAlamofire[53235:1879599] BackgroundSession <01EC4B4A-A81E-4D5B-9004-CF615DEDFD87> connection to background transfer daemon interrupted
? 調(diào)用保存的后臺(tái)下載回調(diào)瞎抛,告訴系統(tǒng)及時(shí)更新屏幕
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)
{
    print("讓后臺(tái)任務(wù)保持下載")
    DispatchQueue.main.async {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
        backgroundHandle()
    }
}

輸出結(jié)果為:

讓后臺(tái)任務(wù)保持下載

需要注意的是艺演,當(dāng)我們切換到后臺(tái)的時(shí)候,任務(wù)雖然還在下載桐臊,但是下載進(jìn)度將不會(huì)再打印到控制臺(tái)上胎撤,你不要以為下載終止了(??,我起初就是這樣以為的断凶,還查了些資料)伤提。


2、屬性

常規(guī)屬性
  • identifier:配置對(duì)象的后臺(tái)會(huì)話標(biāo)識(shí)符
  • httpAdditionalHeaders:與請(qǐng)求一起發(fā)送的附加頭文件的字典
  • networkServiceType:網(wǎng)絡(luò)服務(wù)的類(lèi)型
  • allowsCellularAccess:一個(gè)布爾值认烁,用于確定是否應(yīng)通過(guò)蜂窩網(wǎng)絡(luò)進(jìn)行連接
  • timeoutIntervalForRequest:等待其他數(shù)據(jù)時(shí)使用的超時(shí)間隔
  • timeoutIntervalForResource:資源請(qǐng)求應(yīng)該允許的最大時(shí)間量
  • sharedContainerIdentifier:應(yīng)該下載后臺(tái)URL會(huì)話中的文件的共享容器的標(biāo)識(shí)符
  • waitsForConnectivity:一個(gè)布爾值肿男,指示會(huì)話是否應(yīng)等待連接變?yōu)榭捎没蛘吡⒓词?/li>
設(shè)置Cookie政策
  • httpCookieAcceptPolicy:決定何時(shí)應(yīng)該接受Cookie的策略常量
  • httpShouldSetCookies:一個(gè)布爾值,用于確定請(qǐng)求是否應(yīng)包含來(lái)自Cookie存儲(chǔ)的內(nèi)容
  • httpCookieStorage:管理cookie存儲(chǔ)的單一對(duì)象(共享實(shí)例)
  • HTTPCookie:表示HTTP cookie的對(duì)象砚著。它是一個(gè)不可變的對(duì)象次伶,從包含cookie屬性的字典中初始化
設(shè)置安全策略
  • tlsMaximumSupportedProtocol:在此會(huì)話中進(jìn)行連接時(shí)客戶端應(yīng)請(qǐng)求的最大TLS協(xié)議版本
  • tlsMinimumSupportedProtocol:協(xié)議協(xié)商期間應(yīng)該接受的最小TLS協(xié)議
  • urlCredentialStorage:提供身份驗(yàn)證憑據(jù)的憑證存儲(chǔ)
設(shè)置緩存策略
  • urlCache:用于向會(huì)話中的請(qǐng)求提供緩存響應(yīng)的URL緩存
  • requestCachePolicy:一個(gè)預(yù)定義常量,用于確定何時(shí)從緩存中返回響應(yīng)
支持后臺(tái)轉(zhuǎn)移
  • sessionSendsLaunchEvents:一個(gè)布爾值稽穆,指示在傳輸完成時(shí)是否應(yīng)該在后臺(tái)繼續(xù)或啟動(dòng)應(yīng)用程序
  • isDiscretionary:一個(gè)布爾值冠王,用于確定是否可以根據(jù)系統(tǒng)的判斷來(lái)調(diào)度后臺(tái)任務(wù)以獲得最佳性能
支持自定義協(xié)議
  • protocolClasses:在會(huì)話中處理請(qǐng)求的額外協(xié)議子類(lèi)的數(shù)組
  • URLProtocol:一個(gè)NSURLProtocol對(duì)象處理加載協(xié)議特定的URL數(shù)據(jù)。NSURLProtocol類(lèi)本身是一個(gè)抽象類(lèi)舌镶,可以為與特定URL方案的URL處理基礎(chǔ)設(shè)施柱彻。您可以為您的應(yīng)用支持的任何自定義協(xié)議或URL方案創(chuàng)建子類(lèi)
支持多路徑TCP
  • multipathServiceType:指定用于通過(guò)Wi-Fi和蜂窩接口傳輸數(shù)據(jù)的多路徑TCP連接策略的服務(wù)類(lèi)型
  • URLSessionConfiguration.MultipathServiceType:指定多路徑TCP使用的服務(wù)類(lèi)型的常量
設(shè)置HTTP策略和代理屬性
  • httpMaximumConnectionsPerHost:同時(shí)連接到給定主機(jī)的最大數(shù)量
  • httpShouldUsePipelining:一個(gè)布爾值,用于確定會(huì)話是否應(yīng)使用HTTP流水線
  • connectionProxyDictionary:包含有關(guān)在此會(huì)話中使用的代理信息的字典
支持連接變化
  • waitsForConnectivity:一個(gè)布爾值餐胀,指示會(huì)話是否應(yīng)等待連接變?yōu)榭捎没蛘吡⒓词?/li>
默認(rèn)緩存策略
  • NSURLRequestUseProtocolCachePolicy = 0:如果請(qǐng)求擁有一個(gè)緩存的響應(yīng)哟楷,那么URL加載系統(tǒng)會(huì)檢查這個(gè)響應(yīng)來(lái)決定。假如內(nèi)容必須重新生效否灾,將建立一個(gè)連向源端的連接來(lái)查看內(nèi)容是否發(fā)生變化卖擅。假如內(nèi)容沒(méi)有變化,那么響應(yīng)就從本地緩存返回?cái)?shù)據(jù)墨技。如果內(nèi)容變化了惩阶,那么數(shù)據(jù)將從源端獲取。

3扣汪、HTTP

a断楷、三次握手
為什么不是兩次握手?

沒(méi)有第三次握手崭别,服務(wù)端就不知道客戶端是否可以接收到消息冬筒,即確定服務(wù)端和客戶端兩者都可以發(fā)送消息也可以接收消息恐锣。

為什么不是四次握手?

通過(guò)三次握手已經(jīng)確定服務(wù)端和客戶端兩者都可以發(fā)送消息也可以接收消息了舞痰,沒(méi)必要再進(jìn)行第四次握手土榴。


b、四次揮手
為什么不是兩次揮手匀奏?

因?yàn)榉?wù)器需要通過(guò)第三次揮手將還沒(méi)有傳輸完成的數(shù)據(jù)全部傳輸完成鞭衩。

為什么不是三次揮手学搜?

因?yàn)榉?wù)器需要通過(guò)第三次揮手將還沒(méi)有傳輸完成的數(shù)據(jù)全部傳輸完成娃善,而客戶端需要通過(guò)第四次揮手告訴服務(wù)端第三次揮手發(fā)送過(guò)來(lái)的數(shù)據(jù)已經(jīng)全部接收完畢,通過(guò)四次揮手可以保證整個(gè)通訊過(guò)程的完整性瑞佩。


c聚磺、使用Wireshark工具抓包
? 兩個(gè)終端之間達(dá)到握手

當(dāng)在下圖左側(cè)的終端輸入的時(shí)候會(huì)自動(dòng)顯示到右側(cè)終端進(jìn)行同步,即兩個(gè)終端之間成功達(dá)到了握手炬丸。

? 進(jìn)入Loopback界面
? 在終端重新進(jìn)行握手讓Loopback開(kāi)始監(jiān)聽(tīng)
? 監(jiān)聽(tīng)到三次握手流程中Seq和Ack的變化
? 在終端斷掉握手讓Loopback監(jiān)聽(tīng)四次揮手
? 監(jiān)聽(tīng)到四次揮手流程中Seq和Ack的變化

d瘫寝、OSL七層協(xié)議
  • 應(yīng)用層(HTTP、FTP稠炬、DNS):文件傳輸焕阿,電子郵件,文件服務(wù)首启,虛擬終端
  • 表示層(沒(méi)有協(xié)議):數(shù)據(jù)格式化暮屡,代碼轉(zhuǎn)換,數(shù)據(jù)加密
  • 會(huì)話層(沒(méi)有協(xié)議):解除或建立與別的接點(diǎn)的聯(lián)系
  • 傳輸層(TCP毅桃、UDP):提供端對(duì)端的接口
  • 網(wǎng)絡(luò)層(IP):為數(shù)據(jù)包選擇路由
  • 數(shù)據(jù)鏈路層(SLIP):傳輸有地址的幀以及錯(cuò)誤檢測(cè)功能
  • 物理層(ISO2110):以二進(jìn)制數(shù)據(jù)形式在物理媒體上傳輸數(shù)據(jù)
七層協(xié)議
包裝與解包

二褒纲、初涉Alamofire

AlamofireHTTP 網(wǎng)絡(luò)請(qǐng)求提供了一個(gè)優(yōu)雅且可組合的接口。它沒(méi)有實(shí)現(xiàn)自己的 HTTP 網(wǎng)絡(luò)功能钥飞。取而代之的是莺掠,它建立在由Foundation 框架提供的URL 加載系統(tǒng)之上。系統(tǒng)的核心是 URLSessionURLSessionTask子類(lèi)读宙。

1彻秆、發(fā)起請(qǐng)求

Alamofire 為發(fā)出 HTTP 請(qǐng)求提供了多種方便的方法。

a结闸、最簡(jiǎn)單的請(qǐng)求方式只需提供一個(gè)可以轉(zhuǎn)換為 URL 的 String
  • 鏈?zhǔn)秸Z(yǔ)法
  • 返回JSON
  • 直接放入String唇兑,而不是URL
AF.request("https://httpbin.org/get").response
{ response in
    debugPrint(response)
}

輸出結(jié)果為:

[Request]: GET https://httpbin.org/get
    [Headers]: None
    [Body]: None
[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 426
        Content-Type: application/json
        Date: Mon, 25 Jan 2021 05:35:54 GMT
        Server: gunicorn/19.9.0
    [Body]:
        {
          "args": {}, 
          "headers": {
            "Accept": "*/*", 
            "Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8", 
            "Accept-Language": "en;q=1.0", 
            "Host": "httpbin.org", 
            "User-Agent": "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1", 
            "X-Amzn-Trace-Id": "Root=1-600e58ba-22848fce5ae105571e598f1e"
          }, 
          "origin": "222.76.251.163", 
          "url": "https://httpbin.org/get"
        }
[Network Duration]: 1.434872031211853s
[Serialization Duration]: 0.0s
[Result]: success(Optional(426 bytes))

這實(shí)際上是一種縮寫(xiě)形式,它的完整定義如下膀估。此方法創(chuàng)建一個(gè) DataRequest幔亥,允許傳入多個(gè)參數(shù)。

open func request<Parameters: Encodable>(
    _ convertible: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
    headers: HTTPHeaders? = nil,
    interceptor: RequestInterceptor? = nil
) -> DataRequest

b察纯、為遵循 Alamofire 的 URLRequestConvertible 協(xié)議的任何類(lèi)型創(chuàng)建 DataRequest
open func request(
    _ urlRequest: URLRequestConvertible,
    interceptor: RequestInterceptor? = nil
) -> DataRequest

2帕棉、HTTP Methods

a针肥、作為 method 參數(shù)傳遞給 AF.request API

不同的 HTTP 方法可能有不同的語(yǔ)義,需要不同的參數(shù)編碼香伴,這取決于服務(wù)器的期望慰枕。例如,URLSessionAlamofire 不支持在 GET 請(qǐng)求中傳遞 body 數(shù)據(jù)即纲,否則將返回錯(cuò)誤具帮。

public struct HTTPMethod: RawRepresentable, Equatable, Hashable 
{
    public static let connect = HTTPMethod(rawValue: "CONNECT")
    public static let delete = HTTPMethod(rawValue: "DELETE")
    public static let get = HTTPMethod(rawValue: "GET")
    public static let head = HTTPMethod(rawValue: "HEAD")
    public static let options = HTTPMethod(rawValue: "OPTIONS")
    public static let patch = HTTPMethod(rawValue: "PATCH")
    public static let post = HTTPMethod(rawValue: "POST")
    public static let put = HTTPMethod(rawValue: "PUT")
    public static let trace = HTTPMethod(rawValue: "TRACE")

    public let rawValue: String

    public init(rawValue: String) 
    {
        self.rawValue = rawValue
    }
}

這些值可以作為 method 參數(shù)傳遞給 AF.request API

AF.request("https://httpbin.org/get")
AF.request("https://httpbin.org/post", method: .post)
AF.request("https://httpbin.org/put", method: .put)
AF.request("https://httpbin.org/delete", method: .delete)

b低斋、Alamofire 還提供了對(duì) URLRequest 的擴(kuò)展

以橋接將字符串返回到 HTTPMethod 值的 httpMethod 屬性蜂厅。

extension URLRequest
{
    /// Returns the `httpMethod` as Alamofire's `HTTPMethod` type.
    public var method: HTTPMethod?
    {
        get { httpMethod.flatMap(HTTPMethod.init) }
        set { httpMethod = newValue?.rawValue }
    }
}

c、擴(kuò)展HTTPMethod類(lèi)型添加自定義值

如果需要使用 AlamofireHTTPMethod 類(lèi)型不支持的 HTTP 方法膊畴,可以擴(kuò)展該類(lèi)型以添加自定義值掘猿。

extension HTTPMethod
{
    static let custom = HTTPMethod(rawValue: "CUSTOM")
}

3、請(qǐng)求參數(shù)和參數(shù)編碼器

a唇跨、請(qǐng)求參數(shù)

Alamofire 支持將遵守 Encodable 協(xié)議的類(lèi)型作為請(qǐng)求參數(shù)稠通。這些請(qǐng)求參數(shù)通過(guò)遵循 ParameterEncoder 協(xié)議的參數(shù)編碼器進(jìn)行傳遞并添加到 URLRequest 中,最后通過(guò)網(wǎng)絡(luò)發(fā)送买猖。Alamofire 包含兩種遵循 ParameterEncoder 協(xié)議的參數(shù)編碼器:JSONParameterEncoderURLEncodedFormParameterEncoder 改橘。這兩種參數(shù)編碼器涵蓋了最常見(jiàn)的編碼方式。

struct Login: Encodable
{
    let email: String
    let password: String
}

let login = Login(email: "2170928274@qq.com", password: "19970118")

AF.request("https://httpbin.org/post", method: .post, parameters: login, encoder: JSONParameterEncoder.default).response
{ response in
    debugPrint(response)
}

輸出結(jié)果為:

[Request]: POST https://httpbin.org/post
    [Headers]:
        Content-Type: application/json
    [Body]:
        {"email":"2170928274@qq.com","password":"19970118"}
[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 682
        Content-Type: application/json
        Date: Mon, 25 Jan 2021 06:01:51 GMT
        Server: gunicorn/19.9.0
    [Body]:
        {
          "args": {}, 
          "data": "{\"email\":\"2170928274@qq.com\",\"password\":\"19970118\"}", 
          "files": {}, 
          "form": {}, 
          "headers": {
            "Accept": "*/*", 
            "Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8", 
            "Accept-Language": "en;q=1.0", 
            "Content-Length": "51", 
            "Content-Type": "application/json", 
            "Host": "httpbin.org", 
            "User-Agent": "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1", 
            "X-Amzn-Trace-Id": "Root=1-600e5ecf-01e2ada305b5bd8a0e0e0dc6"
          }, 
          "json": {
            "email": "2170928274@qq.com", 
            "password": "19970118"
          }, 
          "origin": "222.76.251.163", 
          "url": "https://httpbin.org/post"
        }
[Network Duration]: 2.090991973876953s
[Serialization Duration]: 0.0s
[Result]: success(Optional(682 bytes))

b玉控、使用 URL 編碼參數(shù)的 GET 請(qǐng)求(默認(rèn)編碼方式)
// https://httpbin.org/get?foo=bar
let parameters = ["foo": "bar"]

// 下面三種方法都是等價(jià)的
AF.request("https://httpbin.org/get", parameters: parameters)
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .methodDependent))

輸出結(jié)果為:

[Request]: GET https://httpbin.org/get?foo=bar

c飞主、使用 URL 編碼參數(shù)的 POST 請(qǐng)求
// HTTP body: "qux[]=x&qux[]=y&qux[]=z&baz[]=a&baz[]=b&foo[]=bar"
let parameters: [String: [String]] =
[
    "foo": ["bar"],
    "baz": ["a", "b"],
    "qux": ["x", "y", "z"]
]

// 下面三種方法都是等價(jià)的
AF.request("https://httpbin.org/post", method: .post, parameters: parameters)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .httpBody))

輸出結(jié)果為:

[Request]: POST https://httpbin.org/post
    [Headers]:
        Content-Type: application/x-www-form-urlencoded; charset=utf-8
    [Body]: 73 bytes

"form": {
  "baz[]": [
    "a",
    "b"
  ],
  "foo[]": "bar",
  "qux[]": [
    "x",
    "y",
    "z"
  ]
}, 

d、JSON 編碼參數(shù)的 POST 請(qǐng)求
// HTTP body: {"baz":["a","b"],"foo":["bar"],"qux":["x","y","z"]}
let parameters: [String: [String]] =
[
    "foo": ["bar"],
    "baz": ["a", "b"],
    "qux": ["x", "y", "z"]
]

AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: Alamofire.JSONParameterEncoder.default).response
{ response in
    debugPrint(response)
}

輸出結(jié)果為:

[Request]: POST https://httpbin.org/post
    [Headers]:
        Content-Type: application/json
    [Body]:
        {"foo":["bar"],"qux":["x","y","z"],"baz":["a","b"]}a

4奸远、HTTP Headers

a既棺、構(gòu)造HTTP Headers

Alamofire 包含自己的 HTTPHeaders 類(lèi)型,這是一種保持順序且不區(qū)分大小寫(xiě)的 name/value 對(duì)的表示懒叛。HTTPHeader 類(lèi)型可以封裝單個(gè) name/value 對(duì)丸冕,并為常用的 headers 提供各種靜態(tài)值。向 Request 添加自定義 HTTPHeaders 就像向 request 方法傳遞值一樣簡(jiǎn)單薛窥。

let headers: HTTPHeaders =
[
    "Authorization": "Basic VXNlcm5hbWU6UGFzc3dvcmQ=",
    "Accept": "application/json"
]

AF.request("https://httpbin.org/headers", headers: headers).responseJSON
{ response in
    debugPrint(response)
}

HTTPHeaders 類(lèi)型也可以采用如下方式進(jìn)行構(gòu)造:

let anotherHeaders: HTTPHeaders =
[
    .authorization(username: "Username", password: "Password"),
    .accept("application/json")
]

b胖烛、Session 為每個(gè) Request 提供一組默認(rèn)的 headers
  • Accept-Encoding:默認(rèn)為 "br;q=1.0, gzip;q=0.9, deflate;q=0.8"
  • Accept-Language:默認(rèn)系統(tǒng)中最多含有 6 種首選語(yǔ)言,格式為 "en;q=1.0"
  • User-Agent:包含有關(guān)應(yīng)用程序的版本信息诅迷,例如"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1"

5佩番、響應(yīng)驗(yàn)證

默認(rèn)情況下,無(wú)論響應(yīng)的內(nèi)容如何罢杉,Alamofire 都會(huì)將任何已完成的請(qǐng)求視為成功趟畏。如果響應(yīng)具有不可接受的狀態(tài)代碼或 MIME 類(lèi)型,則在響應(yīng)處理程序之前調(diào)用 validate() 將導(dǎo)致生成錯(cuò)誤滩租。

a赋秀、自動(dòng)驗(yàn)證

validate() 會(huì)自動(dòng)驗(yàn)證狀態(tài)代碼是否在200..<300范圍內(nèi)利朵,以及響應(yīng)的Content-Type header 是否與請(qǐng)求的 Accept 匹配(如果有提供)。

AF.request("https://httpbin.org/get").validate().responseJSON
{ response in
    debugPrint(response)
}

輸出結(jié)果為:

[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 427
        Content-Type: application/json
        Date: Mon, 25 Jan 2021 07:38:36 GMT
        Server: gunicorn/19.9.0

b猎莲、手動(dòng)驗(yàn)證
AF.request("https://httpbin.org/get")
    .validate(statusCode: 200..<300)
    .validate(contentType: ["application/json"])
    .responseData
    { response in
        switch response.result
        {
        case .success:
            print("Validation Successful")
        case let .failure(error):
            print(error)
        }
    }

輸出結(jié)果為:

Validation Successful

6绍弟、響應(yīng)處理

不管被序列化成哪種類(lèi)型,結(jié)果都會(huì)通過(guò)閉包的參數(shù)response返回著洼,如果是被序列化的數(shù)據(jù)樟遣,就通過(guò)resonse中的result.value來(lái)獲取數(shù)據(jù)。源碼中response閉包函數(shù)的返回值是Self身笤,也就是Request豹悬,這就讓我們能夠使用鏈?zhǔn)皆L問(wèn)來(lái)做一些很有意思的事情,任務(wù)按照順序依次放入到隊(duì)列中展鸡。

a屿衅、Handler

不計(jì)算任何響應(yīng)數(shù)據(jù)埃难。它只是直接從 URLSessionDelegate 轉(zhuǎn)發(fā)所有信息莹弊。

// 未序列化的 Response
func response(
    queue: DispatchQueue = .main,
    completionHandler: @escaping (AFDataResponse<Data?>) -> Void
) -> Self

// 序列化的 Response
func response<Serializer: DataResponseSerializerProtocol>(
    queue: DispatchQueue = .main,
    responseSerializer: Serializer,
    completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void
) -> Self

使用方式為:

AF.request("https://httpbin.org/get").response
{ response in
    debugPrint("Response: \(response)")
}

輸出結(jié)果為:

"Response: success(Optional(426 bytes))"

b、Data Handler

使用 DataResponseSerializer 提取并驗(yàn)證服務(wù)器返回的數(shù)據(jù)涡尘。如果沒(méi)有發(fā)生錯(cuò)誤并且返回?cái)?shù)據(jù)忍弛,則響應(yīng)結(jié)果將為 .successvalue 將為從服務(wù)器返回的 Data考抄。

func responseData(
    queue: DispatchQueue = .main,
    completionHandler: @escaping (AFDataResponse<Data>) -> Void
) -> Self

使用方式為:

AF.request("https://httpbin.org/get").responseData
{ response in
    debugPrint("Response: \(response)")
}

輸出結(jié)果為:

"Response: success(427 bytes)"

c细疚、String Handler

使用 StringResponseSerializer 將服務(wù)器返回的數(shù)據(jù)轉(zhuǎn)換為具有指定編碼的String。如果沒(méi)有發(fā)生錯(cuò)誤川梅,并且服務(wù)器數(shù)據(jù)成功序列化為 String疯兼,則響應(yīng)結(jié)果將為 .success,并且值的類(lèi)型為 String贫途。

func responseString(
    queue: DispatchQueue = .main,
    encoding: String.Encoding? = nil,
    completionHandler: @escaping (AFDataResponse<String>) -> Void
) -> Self

使用方式為:

AF.request("https://httpbin.org/get").responseString
{ response in
    debugPrint("Response: \(response)")
}

輸出結(jié)果為:

"Response: success(\"{\\n  \\\"args\\\": {}, \\n  \\\"headers\\\": {\\n    \\\"Accept\\\": \\\"*/*\\\", \\n    \\\"Accept-Encoding\\\": \\\"br;q=1.0, gzip;q=0.9, deflate;q=0.8\\\", \\n    \\\"Accept-Language\\\": \\\"en;q=1.0\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"User-Agent\\\": \\\"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-600e8ac6-70026c993647252b60805135\\\"\\n  }, \\n  \\\"origin\\\": \\\"218.104.139.115\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/get\\\"\\n}\\n\")"

d吧彪、JSON Handler

使用指定的 JSONSerialization.ReadingOptions 將服務(wù)器返回的數(shù)據(jù)轉(zhuǎn)換為 Any 類(lèi)型。如果沒(méi)有出現(xiàn)錯(cuò)誤丢早,并且服務(wù)器數(shù)據(jù)成功序列化為 JSON 對(duì)象姨裸,則響應(yīng) AFResult 將為 .success,值將為 Any 類(lèi)型怨酝。

func responseJSON(
    queue: DispatchQueue = .main,
    options: JSONSerialization.ReadingOptions = .allowFragments,
    completionHandler: @escaping (AFDataResponse<Any>) -> Void
) -> Self

使用方式為:

AF.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint("Response: \(response)")
}

輸出結(jié)果為:

"Response: success({\n    args =     {\n    };\n    headers =     {\n        Accept = \"*/*\";\n        \"Accept-Encoding\" = \"br;q=1.0, gzip;q=0.9, deflate;q=0.8\";\n        \"Accept-Language\" = \"en;q=1.0\";\n        Host = \"httpbin.org\";\n        \"User-Agent\" = \"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1\";\n        \"X-Amzn-Trace-Id\" = \"Root=1-600e8b4a-28fe0a1064c7a6e1033414fa\";\n    };\n    origin = \"218.104.139.115\";\n    url = \"https://httpbin.org/get\";\n})"

e傀缩、可解碼類(lèi)型的Handler

使用 DecodableResponseSerializer 和 指定的 DataDecoderDecoder 的協(xié)議抽象,可以從 Data 解碼)將服務(wù)器返回的數(shù)據(jù)轉(zhuǎn)換為傳遞進(jìn)來(lái)的 Decodable 類(lèi)型农猬。如果沒(méi)有發(fā)生錯(cuò)誤赡艰,并且服務(wù)器數(shù)據(jù)已成功解碼為 Decodable 類(lèi)型,則響應(yīng) Result 將為 .success斤葱,并且 value 將為傳遞進(jìn)來(lái)的類(lèi)型慷垮。

func responseDecodable<T: Decodable>(
    of type: T.Type = T.self,
    queue: DispatchQueue = .main,
    decoder: DataDecoder = JSONDecoder(),
    completionHandler: @escaping (AFDataResponse<T>) -> Void
) -> Self

使用方式為:

struct HTTPBinResponse: Decodable
{
    let url: String
}

AF.request("https://httpbin.org/get").responseDecodable(of: HTTPBinResponse.self)
{ response in
    debugPrint("Response: \(response)")
}

輸出結(jié)果為:

"Response: success(UseAlamofire.HTTPBinResponse(url: \"https://httpbin.org/get\"))"

f勋又、鏈?zhǔn)巾憫?yīng)

沒(méi)有一個(gè)響應(yīng) handlers 對(duì)從服務(wù)器返回的 HTTPURLResponse 執(zhí)行任何驗(yàn)證换帜。例如惯驼,400..<500500..<600 范圍內(nèi)的響應(yīng)狀態(tài)代碼不會(huì)自動(dòng)觸發(fā)錯(cuò)誤祟牲。Alamofire 使用鏈?zhǔn)降捻憫?yīng)驗(yàn)證來(lái)實(shí)現(xiàn)這一點(diǎn)说贝。對(duì)同一請(qǐng)求使用多個(gè)響應(yīng) handlers 需要多次序列化服務(wù)器數(shù)據(jù)议惰,每個(gè)響應(yīng) handlers 均處理一次。通常應(yīng)避免對(duì)同一請(qǐng)求使用多個(gè)響應(yīng) handlers乡恕,特別是在生產(chǎn)環(huán)境中言询。

AF.request("https://httpbin.org/get")
    .responseString
    { response in
        print("Response String: \(String(describing: response.value) )")
    }
    .responseJSON
    { response in
        print("Response JSON: \(String(describing: response.value))")
    }

輸出結(jié)果為:

Response String: Optional("{\n  \"args\": {}, \n  \"headers\": {\n    \"Accept\": \"*/*\", \n    \"Accept-Encoding\": \"br;q=1.0, gzip;q=0.9, deflate;q=0.8\", \n    \"Accept-Language\": \"en;q=1.0\", \n    \"Host\": \"httpbin.org\", \n    \"User-Agent\": \"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1\", \n    \"X-Amzn-Trace-Id\": \"Root=1-600e8e25-66ed7b9b0986f02971550391\"\n  }, \n  \"origin\": \"218.104.139.115\", \n  \"url\": \"https://httpbin.org/get\"\n}\n")
Response JSON: Optional({
    args =     {
    };
    headers =     {
        Accept = "*/*";
        "Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
        "Accept-Language" = "en;q=1.0";
        Host = "httpbin.org";
        "User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
        "X-Amzn-Trace-Id" = "Root=1-600e8e25-66ed7b9b0986f02971550391";
    };
    origin = "218.104.139.115";
    url = "https://httpbin.org/get";
})

g、響應(yīng)隊(duì)列

默認(rèn)情況下傲宜,傳遞給響應(yīng) handler 的閉包在 .main 隊(duì)列上執(zhí)行运杭,但可以傳遞一個(gè)指定的 DispatchQueue 來(lái)執(zhí)行閉包。實(shí)際的序列化工作(將 Data 轉(zhuǎn)換為其他類(lèi)型)總是在后臺(tái)隊(duì)列上執(zhí)行函卒。

let utilityQueue = DispatchQueue.global(qos: .utility)
AF.request("https://httpbin.org/get").responseJSON(queue: utilityQueue)
{ response in
    print("在全局隊(duì)列上執(zhí)行此網(wǎng)絡(luò)請(qǐng)求:\(Thread.current)")
    debugPrint(response)
}

輸出結(jié)果為:

在全局隊(duì)列上執(zhí)行此網(wǎng)絡(luò)請(qǐng)求:<NSThread: 0x600000401a00>{number = 8, name = (null)}

7辆憔、身份驗(yàn)證

a、自動(dòng)提供 URLCredential

Requestauthenticate方法將在使用 URLAuthenticationChallenge 進(jìn)行質(zhì)詢時(shí)自動(dòng)提供 URLCredential报嵌。

let user = "user"
let password = "password"

AF.request("https://httpbin.org/basic-auth/\(user)/\(password)")
    .authenticate(username: user, password: password)
    .responseJSON
    { response in
        debugPrint(response)
    }

輸出結(jié)果為:

[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 47
        Content-Type: application/json
        Date: Mon, 25 Jan 2021 10:01:37 GMT
        Server: gunicorn/19.9.0
    [Body]:
        {
          "authenticated": true, 
          "user": "user"
        }

b虱咧、自己提供 URLCredential 進(jìn)行驗(yàn)證
let user = "user"
let password = "password"

let credential = URLCredential(user: user, password: password, persistence: .forSession)

AF.request("https://httpbin.org/basic-auth/\(user)/\(password)")
    .authenticate(with: credential)
    .responseJSON
    { response in
        debugPrint(response)
    }

輸出結(jié)果同上。


8锚国、下載文件

a逸雹、下載數(shù)據(jù)到文件中

除了將數(shù)據(jù)提取到內(nèi)存中之外帖世,Alamofire 還提供了 Session.downloadDownloadRequestDownloadResponse<Success盈魁,F(xiàn)ailure:Error> 以方便下載數(shù)據(jù)到磁盤(pán)飘痛。雖然下載到內(nèi)存中對(duì)小負(fù)載(如大多數(shù) JSON API 響應(yīng))非常有用,但獲取更大的資源(如圖像和視頻)應(yīng)下載到磁盤(pán)竹祷,以避免應(yīng)用程序出現(xiàn)內(nèi)存問(wèn)題践险。DownloadRequest 具有與 DataRequest 相同的大多數(shù)響應(yīng) handlers鳍刷。但是,由于它將數(shù)據(jù)下載到磁盤(pán),因此序列化響應(yīng)涉及從磁盤(pán)讀取负芋,還可能涉及將大量數(shù)據(jù)讀入內(nèi)存。在設(shè)計(jì)下載處理時(shí),記住這些事實(shí)是很重要的奶镶。

AF.download("https://httpbin.org/image/png").responseData
{ response in
    if let data = response.value
    {
        let image = UIImage(data: data)
        self.imageView.image = image!
    }
}

b捺信、下載文件的存放位置

所有下載的數(shù)據(jù)最初都存儲(chǔ)在系統(tǒng)臨時(shí)目錄中掌挚。它最終會(huì)在將來(lái)的某個(gè)時(shí)候被系統(tǒng)刪除,所以如果它需要更長(zhǎng)的壽命,將文件移到其他地方是很重要的。我們可以提供 Destination 閉包懊纳,將文件從臨時(shí)目錄移動(dòng)到最終的存放位置萍倡。在臨時(shí)文件實(shí)際移動(dòng)到 destinationURL 之前,將執(zhí)行閉包中指定的 Options翩蘸。當(dāng)前支持的兩個(gè) Options 是:.createIntermediateDirectories 如果指定郎任,則為目標(biāo) URL 創(chuàng)建中間目錄车猬。.removePreviousFile如果指定,則從目標(biāo) URL中刪除以前的文件阅仔。

AF.download("https://httpbin.org/image/png", to: destination).response
{ response in
    debugPrint(response)

    if response.error == nil, let imagePath = response.fileURL?.path
    {
        let image = UIImage(contentsOfFile: imagePath)
        self.imageView.image = image!
    }
}

輸出結(jié)果為:

[Request]: GET https://httpbin.org/image/png
    [Headers]: None
    [Body]: None
[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 8090
        Content-Type: image/png
        Date: Mon, 25 Jan 2021 10:14:50 GMT
        Server: gunicorn/19.9.0
[File URL]: /Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/730436F5-DE88-42CA-96AF-B5FC5A4C9019/Documents/image.png
[Resume Data]: None
[Network Duration]: 1.8333059549331665s
[Serialization Duration]: 0.0s
[Result]: success(Optional(file:///Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/730436F5-DE88-42CA-96AF-B5FC5A4C9019/Documents/image.png))

文件存儲(chǔ)的位置為

還可以使用建議的文件存儲(chǔ)位置,效果同上。

let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
AF.download("https://httpbin.org/image/png", to: destination).response
{ response in
    debugPrint(response)

    if response.error == nil, let imagePath = response.fileURL?.path
    {
        let image = UIImage(contentsOfFile: imagePath)
        self.imageView.image = image!
    }
}

c抖甘、下載進(jìn)度

任何 DownloadRequest 都可以使用 downloadProgress 報(bào)告下載進(jìn)度。只有在服務(wù)器正確返回可用于計(jì)算進(jìn)度的 Content-Length header 時(shí)才能工作椒涯。如果沒(méi)有這個(gè) header回梧,進(jìn)度將保持在 0.0废岂,直到下載完成帜矾,此時(shí)進(jìn)度將跳到 1.0锌唾。還可以接收一個(gè)queue參數(shù)具被,該參數(shù)定義應(yīng)該對(duì)哪個(gè) DispatchQueue 調(diào)用下載進(jìn)度閉包矾端。

let utilityQueue = DispatchQueue.global(qos: .utility)

AF.download("https://httpbin.org/image/png")
    .downloadProgress(queue: utilityQueue)
    { progress in
        print("下載進(jìn)度: \(progress.fractionCompleted)")
    }
    .responseData
    { response in
        if let data = response.value
        {
            let image = UIImage(data: data)
            self.imageView.image = image!
        }
    }

輸出結(jié)果為:

下載進(jìn)度: 1.0

d凡壤、取消和恢復(fù)下載

除了所有請(qǐng)求類(lèi)都有 cancel() 方法外厨诸,DownloadRequest 還可以生成恢復(fù)數(shù)據(jù)搅方,這些數(shù)據(jù)可以用于以后恢復(fù)下載。此 API 有兩種形式:1)cancel(producingResumeData: Bool),它允許控制是否生成恢復(fù)數(shù)據(jù)骚亿,但僅在 DownloadResponse 可用泥技;2)cancel(byProducingResumeData: (_ resumeData: Data?) -> Void),它執(zhí)行相同的操作功茴,但恢復(fù)數(shù)據(jù)在 completion handler中可用衡载。如果DownloadRequest 被取消或中斷晴股,則底層的 URLSessionDownloadTask 可能會(huì)生成恢復(fù)數(shù)據(jù)。如果發(fā)生這種情況狗唉,可以重新使用恢復(fù)數(shù)據(jù)來(lái)重新啟動(dòng)停止的 DownloadRequest初烘。

let download = AF.download("https://httpbin.org/image/png")

var resumeData: Data!

// 正常下載
download.responseData
{ response in
    if let data = response.value
    {
        let image = UIImage(data: data)
        self.imageView.image = image!
    }
}

// 從cancel的回調(diào)閉包中獲得resumeData
download.cancel
{ data in
    resumeData = data
}

// 使用resumeData繼續(xù)下載
AF.download(resumingWith: resumeData).responseData
{ response in
    if let data = response.value
    {
        let image = UIImage(data: data)
        self.imageView.image = image!
    }
}

9、上傳數(shù)據(jù)到服務(wù)器

當(dāng)使用 JSONURL 編碼的參數(shù)向服務(wù)器發(fā)送相對(duì)少量的數(shù)據(jù)時(shí)分俯,request() 通常就足夠了肾筐。如果需要從內(nèi)存、文件 URLInputStream 中的 Data 發(fā)送大量數(shù)據(jù)缸剪,那么 upload() 就是您想要使用的吗铐。

a、上傳 Data
let data = Data("XieJiaPei".utf8)

AF.upload(data, to: "https://httpbin.org/post").responseJSON
{ response in
    debugPrint(response)
}

輸出結(jié)果為:

[Result]: success({
    args =     {
    };
    data = "";
    files =     {
    };
    form =     {
        XieJiaPei = "";
    };
    headers =     {
        Accept = "*/*";
        "Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
        "Accept-Language" = "en;q=1.0";
        "Content-Length" = 9;
        "Content-Type" = "application/x-www-form-urlencoded";
        Host = "httpbin.org";
        "User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
        "X-Amzn-Trace-Id" = "Root=1-600f6966-48de0e395d96f17f396e2b67";
    };
    json = "<null>";
    origin = "222.76.251.163";
    url = "https://httpbin.org/post";
})
b杏节、上傳多表單數(shù)據(jù)
AF.upload(multipartFormData: { multipartFormData in
    multipartFormData.append(Data("Boy".utf8), withName: "JiaPei")
    multipartFormData.append(Data("Girl".utf8), withName: "YuQing")
}, to: "https://httpbin.org/post")
.responseJSON { response in
    debugPrint(response)
}

輸出結(jié)果為:

[Result]: success({
    args =     {
    };
    data = "";
    files =     {
    };
    form =     {
        JiaPei = Boy;
        YuQing = Girl;
    };
    headers =     {
        Accept = "*/*";
        "Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
        "Accept-Language" = "en;q=1.0";
        "Content-Length" = 228;
        "Content-Type" = "multipart/form-data; boundary=alamofire.boundary.6d21176fdb63050f";
        Host = "httpbin.org";
        "User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
        "X-Amzn-Trace-Id" = "Root=1-600f6d84-25a167ab5ddb02c206ff4697";
    };
    json = "<null>";
    origin = "218.104.139.115";
    url = "https://httpbin.org/post";
})
c唬渗、上傳文件
let fileURL = Bundle.main.url(forResource: "girl", withExtension: "mp4")!
AF.upload(fileURL, to: "https://httpbin.org/post").responseJSON
{ response in
    debugPrint(response)
}

輸出結(jié)果為:

無(wú)
d、上傳進(jìn)度

當(dāng)用戶等待上傳完成時(shí)奋渔,有時(shí)向用戶顯示上傳的進(jìn)度會(huì)很方便镊逝。任何 UploadRequest 都可以使用 uploadProgressdownloadProgress 報(bào)告響應(yīng)數(shù)據(jù)下載的上傳進(jìn)度和下載進(jìn)度。

let fileURL = Bundle.main.url(forResource: "girl", withExtension: "mp4")!

if FileManager.default.fileExists(atPath: fileURL.path)
{
    AF.upload(fileURL, to: "https://httpbin.org/post")
        .uploadProgress
        { progress in
            print("上傳進(jìn)度: \(progress.fractionCompleted)")
        }
        .responseJSON
        { response in
            print("上傳完成")
            print(response)
        }
}
else
{
    print("沒(méi)有找到文件")
}

輸出結(jié)果為:

上傳進(jìn)度: 0.01685915418681258
上傳進(jìn)度: 0.5394929339780026
上傳進(jìn)度: 0.6743661674725031
上傳進(jìn)度: 0.9441126344615044
上傳進(jìn)度: 1.0

10嫉鲸、網(wǎng)絡(luò)可達(dá)性

監(jiān)聽(tīng)移動(dòng)網(wǎng)絡(luò)和 WiFi 網(wǎng)絡(luò)接口的主機(jī)和地址的可達(dá)性變化撑蒜。

let manager = NetworkReachabilityManager(host: "www.apple.com")

manager?.startListening
{ status in
    print("網(wǎng)絡(luò)狀態(tài)發(fā)生改變: \(status)")
}

輸出結(jié)果為:

網(wǎng)絡(luò)狀態(tài)發(fā)生改變: reachable(Alamofire.NetworkReachabilityManager.NetworkReachabilityStatus.ConnectionType.ethernetOrWiFi)

三、玩轉(zhuǎn)Alamofire

1玄渗、Session

a座菠、Session.default

AlamofireSession 在職責(zé)上大致等同于它維護(hù)的 URLSession 實(shí)例:它提供 API 來(lái)生成各種 Request 子類(lèi),這些子類(lèi)封裝了不同的 URLSessionTask 子類(lèi)藤树,以及封裝應(yīng)用于實(shí)例生成的所有 Request 的各種配置浴滴。Session 提供了一個(gè) default 單例實(shí)例,并且 AF 實(shí)際上就是 Session.default也榄。因此巡莹,以下兩個(gè)語(yǔ)句是等效的:

AF.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}

let session = Session.default
session.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}

b、創(chuàng)建自定義的 Session 實(shí)例

使用以下便利初始化器甜紫,并將結(jié)果存儲(chǔ)在整個(gè)應(yīng)用程序使用的單個(gè)實(shí)例中降宅。此初始化器允許自定義 Session 的所有行為。

let session = Session.init(...)

public convenience init
(
    configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
    delegate: SessionDelegate = SessionDelegate(),
    rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"),
    startRequestsImmediately: Bool = true,
    requestQueue: DispatchQueue? = nil,
    serializationQueue: DispatchQueue? = nil,
    interceptor: RequestInterceptor? = nil,
    serverTrustManager: ServerTrustManager? = nil,
    redirectHandler: RedirectHandler? = nil,
    cachedResponseHandler: CachedResponseHandler? = nil,
    eventMonitors: [EventMonitor] = []
)

c囚霸、使用 URLSessionConfiguration 創(chuàng)建 Session

要自定義底層 URLSession 的行為腰根,可以提供自定義的 URLSessionConfiguration 實(shí)例。建議從改變URLSessionConfiguration.af.default 實(shí)例開(kāi)始拓型,因?yàn)樗砑恿?Alamofire 提供的默認(rèn) Accept-Encoding额嘿、Accept-LanguageUser-Agent headers瘸恼。

let configuration = URLSessionConfiguration.af.default
configuration.allowsCellularAccess = false

let customConfigurationSession = Session(configuration: configuration)
customConfigurationSession.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}

d、SessionDelegate

SessionDelegate 實(shí)例封裝了對(duì)各種 URLSessionDelegate 和相關(guān)協(xié)議回調(diào)的所有處理册养。默認(rèn)情況下东帅,Session 將在添加至少一個(gè)響應(yīng) handler 后立即對(duì) Request 調(diào)用 resume()。將 startRequestsImmediately 設(shè)置為 false 需要手動(dòng)調(diào)用所有請(qǐng)求的 resume() 方法球拦。

let session = Session(startRequestsImmediately: false)
session.request("https://httpbin.org/get").resume().responseJSON
{ response in
    debugPrint(response)
}

輸出結(jié)果為:

[Request]: GET https://httpbin.org/get
    [Headers]: None
    [Body]: None
[Response]: None
[Network Duration]: None
[Serialization Duration]: 0.00031406700145453215s
[Result]: failure(Alamofire.AFError.sessionDeinitialized)

e靠闭、Session 的 DispatchQueue

默認(rèn)情況下,Session 實(shí)例對(duì)所有異步工作使用單個(gè) DispatchQueue坎炼。這包括 URLSessiondelegate OperationQueueunderlyingQueue愧膀,用于所有 URLRequest 創(chuàng)建、所有響應(yīng)序列化工作以及所有內(nèi)部 SessionRequest 狀態(tài)的改變谣光。如果性能分析顯示瓶頸在于 URLRequest 的創(chuàng)建或響應(yīng)序列化檩淋,則可以為 Session 的每個(gè)工作區(qū)域提供單獨(dú)的 DispatchQueue。提供的任何自定義 rootQueue 都必須是串行隊(duì)列萄金,但 requestQueueserializationQueue 可以是串行或并行隊(duì)列蟀悦。通常建議使用串行隊(duì)列,除非性能分析顯示工作被延遲捡絮,在這種情況下熬芜,使隊(duì)列并行可能有助于提高整體性能。

let rootQueue = DispatchQueue(label: "com.app.session.rootQueue")
let requestQueue = DispatchQueue(label: "com.app.session.requestQueue")
let serializationQueue = DispatchQueue(label: "com.app.session.serializationQueue")

let session = Session(
    rootQueue: rootQueue,
    requestQueue: requestQueue,
    serializationQueue: serializationQueue
)

f福稳、添加其他信息
? 添加 RequestInterceptor

AlamofireRequestInterceptor 協(xié)議(RequestAdapter & RequestRetrier)提供了重要而強(qiáng)大的請(qǐng)求自適應(yīng)和重試功能涎拉。

let policy = RetryPolicy()
let session = Session(interceptor: policy)
? 添加 ServerTrustManager

AlamofireServerTrustManager 類(lèi)封裝了域名和遵循 ServerTrustEvaluating 協(xié)議的類(lèi)型實(shí)例之間的映射,這提供了定制 Session 處理 TLS 安全性的能力的圆。這包括使用證書(shū)和公鑰固定以及證書(shū)吊銷(xiāo)檢查鼓拧。

let manager = ServerTrustManager(evaluators: ["httpbin.org": PinnedCertificatesTrustEvaluator()])
let managerSession = Session(serverTrustManager: manager)
managerSession.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}
? 添加 RedirectHandler

AlamofireRedirectHandler 協(xié)議定制了 HTTP 重定向響應(yīng)的處理。

let redirector = Redirector(behavior: .follow)
let redirectorSession = Session(redirectHandler: redirector)
redirectorSession.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}
? 添加 CachedResponseHandler

AlamofireCachedResponseHandler 協(xié)議定制了響應(yīng)的緩存越妈,可以在 SessionRequest 層級(jí)使用季俩。

let cacher = ResponseCacher(behavior: .cache)
let cacherSession = Session(cachedResponseHandler: cacher)
cacherSession.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}
? 添加 EventMonitor

AlamofireEventMonitor 協(xié)議提供了對(duì) Alamofire 內(nèi)部事件的強(qiáng)大洞察力。它可以用來(lái)提供日志和其他基于事件的特性梅掠。

let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError =
{ (request, task, error) in
    debugPrint(request)
}
let monitorSession = Session(eventMonitors: [monitor])
monitorSession.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}

g酌住、從 URLSession 創(chuàng)建實(shí)例

除了前面提到的便利初始化器之外,還可以直接從 URLSession 初始化 Session阎抒。但是酪我,在使用這個(gè)初始化器時(shí)需要記住幾個(gè)要求,因此建議使用便利初始化器且叁。其中包括:

  • Alamofire 不支持為在后臺(tái)使用而配置的 URLSession都哭。初始化 Session 時(shí),這將導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。
  • 必須創(chuàng)建 SessionDelegate 實(shí)例并將其作為 URLSessiondelegate欺矫,以及傳遞給 Session 的初始化器纱新。
  • 必須將自定義 OperationQueue 作為 URLSessiondelegateQueue。此隊(duì)列必須是串行隊(duì)列穆趴,它必須具有備用 DispatchQueue脸爱,并且必須將該 DispatchQueue 作為其 rootQueue 傳遞給 Session
let rootQueue = DispatchQueue(label: "org.alamofire.customQueue")
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.underlyingQueue = rootQueue
let delegate = SessionDelegate()
let configuration = URLSessionConfiguration.af.default
let urlSession = URLSession(configuration: configuration,
                            delegate: delegate,
                            delegateQueue: queue)
let session = Session(session: urlSession, delegate: delegate, rootQueue: rootQueue)

2毡代、Request

a阅羹、創(chuàng)建請(qǐng)求
URLConvertible協(xié)議

可以使用遵循 URLConvertible 協(xié)議的類(lèi)型來(lái)構(gòu)造 URL,然后使用 URL 在內(nèi)部構(gòu)造 URL 請(qǐng)求教寂。默認(rèn)情況下,String执庐、URLURLComponents遵循了URLConvertible協(xié)議酪耕,允許將它們中的任何一個(gè)作為 URL 參數(shù)傳遞給 requestuploaddownload方法轨淌。

let urlString = "https://httpbin.org/get"
AF.request(urlString)

let url = URL(string: urlString)!
AF.request(url)

let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)!
AF.request(urlComponents)
URLRequestConvertible協(xié)議

遵循 URLRequestConvertible 協(xié)議的類(lèi)型可用于構(gòu)造 URLRequest迂烁。默認(rèn)情況下,URLRequest 遵循 URLRequestConvertible递鹉,允許將其直接傳遞到 request盟步、uploaddownload 方法中。

let postUrl = URL(string: "https://httpbin.org/post")!
var urlRequest = URLRequest(url: postUrl)
urlRequest.method = .post

let parameters = ["foo": "bar"]

do
{
    urlRequest.httpBody = try JSONEncoder().encode(parameters)
}
catch
{
    // Handle error.
    print("出錯(cuò)了")
}

urlRequest.headers.add(.contentType("application/json"))

AF.request(urlRequest)
    .responseJSON
    { response in
        debugPrint(response)
    }

輸出結(jié)果為:

[Result]: success({
    args =     {
    };
    data = "{\"foo\":\"bar\"}";
    files =     {
    };
    form =     {
    };
    headers =     {
        Accept = "*/*";
        "Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
        "Accept-Language" = "en;q=1.0";
        "Content-Length" = 13;
        "Content-Type" = "application/json";
        Host = "httpbin.org";
        "User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
        "X-Amzn-Trace-Id" = "Root=1-600fbced-3ebfeeb20ee086fa017998b4";
    };
    json =     {
        foo = bar;
    };
    origin = "218.104.139.115";
    url = "https://httpbin.org/post";
})

b躏结、請(qǐng)求管道

一旦使用 Request 子類(lèi)的初始參數(shù)或 URLRequestConvertible 創(chuàng)建了它却盘,它就會(huì)通過(guò)組成 Alamofire 請(qǐng)求管道的一系列步驟進(jìn)行傳遞。

請(qǐng)求步驟
  1. 初始參數(shù)(如 HTTP 方法媳拴、headers 和參數(shù))被封裝到內(nèi)部 URLRequestConvertible 值中黄橘。
  2. 對(duì) URLRequestConvertible 值調(diào)用 asURLRequest(),創(chuàng)建第一個(gè) URLRequest 值屈溉。此值將傳遞給 Request 并存儲(chǔ)在 requests 中塞关。
  3. 如果 SessionRequestAdapterRequestInterceptor,則使用先前創(chuàng)建的 URLRequest 調(diào)用它們子巾。然后將調(diào)整后的 URLRequest 傳遞給 Request 并存儲(chǔ)在 requests 中帆赢。
  4. Session 調(diào)用 Request 創(chuàng)建的 URLSessionTask,以基于 URLRequest 執(zhí)行網(wǎng)絡(luò)請(qǐng)求线梗。
  5. 完成 URLSessionTask 并收集 URLSessionTaskMetrics(日志)后椰于,Request 將執(zhí)行其 Validator
  6. 請(qǐng)求執(zhí)行已附加的任何響應(yīng) handlers缠导,如 responseDecodable廉羔。
觸發(fā)重試

在這些步驟中的任何一個(gè),都可以通過(guò)創(chuàng)建或接收的 Error 值來(lái)表示失敗,然后將錯(cuò)誤值傳遞給關(guān)聯(lián)的 Request憋他。例如孩饼,除了步驟 1 和 4 之外,上面的所有其他步驟都可以創(chuàng)建一個(gè)Error竹挡,然后傳遞給響應(yīng) handlers 或可供重試镀娶。一旦將錯(cuò)誤傳遞給 RequestRequest 將嘗試運(yùn)行與 SessionRequest 關(guān)聯(lián)的任何 RequestRetrier揪罕。如果任何 RequestRetrier 選擇重試該 Request梯码,則將再次運(yùn)行完整的管道。RequestRetrier也會(huì)產(chǎn)生 Error好啰,但這些錯(cuò)誤不會(huì)觸發(fā)重試轩娶。

失敗原因
  • 參數(shù)封裝不能失敗。
  • 調(diào)用 asURLRequest() 時(shí)框往,任何 URLRequestConvertible 值都可能創(chuàng)建錯(cuò)誤鳄抒。這允許初始驗(yàn)證各種 URLRequest 屬性或參數(shù)編碼失敗。
  • RequestAdapter 在自適應(yīng)過(guò)程中可能會(huì)失敗椰弊,可能是由于缺少授權(quán) token许溅。
  • URLSessionTask 創(chuàng)建不能失敗。
  • URLSessionTask 可能由于各種原因帶有錯(cuò)誤地完成秉版,包括網(wǎng)絡(luò)可用性和取消贤重。這些 Error 值將傳遞回給 Request
  • 響應(yīng) handlers 可以產(chǎn)生任何錯(cuò)誤清焕,通常是由于無(wú)效響應(yīng)或其他分析錯(cuò)誤并蝗。

c、請(qǐng)求種類(lèi)

Alamofire 執(zhí)行的每個(gè)請(qǐng)求都由特定的類(lèi)耐朴、DataRequest借卧、UploadRequestDownloadRequest 封裝。這些類(lèi)中的每一個(gè)都封裝了每種類(lèi)型請(qǐng)求所特有的功能筛峭,但是 DataRequestDownloadRequest 繼承自一個(gè)公共的父類(lèi) RequestUploadRequest 繼承自 DataRequest)铐刘。Request 實(shí)例從不直接創(chuàng)建,而是通過(guò)各種 request方法之一從會(huì)話 Session 中自動(dòng)生成影晓。

DataRequest

DataRequestRequest 的一個(gè)子類(lèi)镰吵,它封裝了 URLSessionDataTask,將服務(wù)器響應(yīng)下載到存儲(chǔ)在內(nèi)存中的 Data 中挂签。因此疤祭,必須認(rèn)識(shí)到,超大下載量可能會(huì)對(duì)系統(tǒng)性能產(chǎn)生不利影響饵婆。對(duì)于這些類(lèi)型的下載勺馆,建議使用 DownloadRequest 將數(shù)據(jù)保存到磁盤(pán)。

除了 Request 提供的屬性之外,DataRequest 還有一些屬性草穆。其中包括 data(這是服務(wù)器響應(yīng)的累積 Data)和 convertible(這是創(chuàng)建 DataRequest 時(shí)使用的 URLRequestConvertible灌灾,其中包含創(chuàng)建實(shí)例的原始參數(shù))。

默認(rèn)情況下悲柱,DataRequest 不驗(yàn)證響應(yīng)锋喜。相反,必須向其中添加對(duì) validate() 的調(diào)用豌鸡,以驗(yàn)證各種屬性是否有效嘿般。添加 validate() 確保響應(yīng)狀態(tài)代碼在 200..<300 范圍內(nèi),并且響應(yīng)的 Content-Type 與請(qǐng)求的 Accept 匹配涯冠。通過(guò)傳遞 Validation 閉包可以進(jìn)一步定制驗(yàn)證炉奴。

UploadRequest

UploadRequestDataRequest 的一個(gè)子類(lèi),它封裝 URLSessionUploadTask蛇更、將 Data盆佣、磁盤(pán)上的文件或 InputStream 上傳到遠(yuǎn)程服務(wù)器。

除了 DataRequest 提供的屬性外械荷,UploadRequest 還有一些屬性。其中包括一個(gè) FileManager 實(shí)例虑灰,用于在上傳文件時(shí)自定義對(duì)磁盤(pán)的訪問(wèn)吨瞎,以及 uploadupload 封裝了用于描述請(qǐng)求的 URLRequestConvertible 值和確定要執(zhí)行的上傳類(lèi)型的Uploadable 值穆咐。

DownloadRequest

DownloadRequestRequest 的一個(gè)具體子類(lèi)颤诀,它封裝了 URLSessionDownloadTask,將響應(yīng)數(shù)據(jù)下載到磁盤(pán)对湃。DownloadRequest 除了由 Request 提供的屬性外崖叫,還有一些屬性。其中包括取消 DownloadRequest 時(shí)生成的數(shù)據(jù) resumeData(可用于以后繼續(xù)下載)和 fileURL(下載完成后下載文件對(duì)應(yīng)的 URL)拍柒。

除了支持 Request 提供的 cancel() 方法外心傀,DownloadRequest 還包括了其他兩種取消方法。

// 可以選擇在取消時(shí)設(shè)置 resumeData 屬性
cancel(producingResumeData shouldProduceResumeData: Bool)

// 將生成的恢復(fù)數(shù)據(jù)提供給傳遞進(jìn)來(lái)的閉包
cancel(byProducingResumeData completionHandler: @escaping (_ data: Data?) -> Void)

d拆讯、請(qǐng)求狀態(tài)

盡管 Request 不封裝任何特定類(lèi)型的請(qǐng)求脂男,但它包含 Alamofire 執(zhí)行的所有請(qǐng)求所共有的狀態(tài)和功能,表示 Request 生命周期中的主要事件种呐。請(qǐng)求在創(chuàng)建后以 .initialized 狀態(tài)啟動(dòng)宰翅。通過(guò)調(diào)用適當(dāng)?shù)纳芷诜椒ǎ梢話炱鹚摇⒒謴?fù)和取消 Request汁讼。

public enum State 
{
    case initialized
    case resumed
    case suspended
    case cancelled
    case finished
}
  • resume(): 恢復(fù)或啟動(dòng)請(qǐng)求的網(wǎng)絡(luò)流量。如果 startRequestsImmediatelytrue,則在將響應(yīng) handlers 添加到 Request 后自動(dòng)調(diào)用此函數(shù)嘿架。
  • suspend(): 掛起或暫停請(qǐng)求及其網(wǎng)絡(luò)流量瓶珊。此狀態(tài)下的 Request 可以繼續(xù),但只有 DownloadRequest 才能繼續(xù)傳輸數(shù)據(jù)眶明。其他 Request 將重新開(kāi)始艰毒。
  • cancel(): 取消請(qǐng)求。一旦進(jìn)入此狀態(tài)搜囱,就無(wú)法恢復(fù)或掛起 Request丑瞧。調(diào)用 cancel() 時(shí),將使用 AFError.explicitlyCancelled 實(shí)例設(shè)置請(qǐng)求的 error 屬性蜀肘。
  • finished(): 如果一個(gè) Request 被恢復(fù)并且在以后沒(méi)有被取消绊汹,那么它將在所有響應(yīng)驗(yàn)證器和響應(yīng)序列化器運(yùn)行之后到達(dá) .finished 狀態(tài)。但是扮宠,如果在請(qǐng)求達(dá)到 .finished 狀態(tài)后將其他響應(yīng)序列化器添加到該請(qǐng)求西乖,則它將轉(zhuǎn)換回 .resumed 狀態(tài)并再次執(zhí)行網(wǎng)絡(luò)請(qǐng)求。

e坛增、請(qǐng)求進(jìn)度

為了跟蹤請(qǐng)求的進(jìn)度获雕,Request 提供了 uploadProgressdownloadProgress 屬性以及基于閉包的 uploadProgressdownloadProgress 方法。與所有基于閉包的 Request APIs 一樣收捣,進(jìn)度 APIs 可以與其他方法鏈接到 Request 之后届案,但是應(yīng)該在添加任何響應(yīng) handlers(如 responseJSON)之前添加到請(qǐng)求中。但并不是所有的 Request 子類(lèi)都能夠準(zhǔn)確地報(bào)告它們的進(jìn)度罢艾,有些可能有其他依賴項(xiàng)來(lái)報(bào)告它們的進(jìn)度楣颠。對(duì)于下載進(jìn)度,只有一個(gè)要求咐蚯,服務(wù)器響應(yīng)必須包含 Content-Length header童漩。

AF.request("https://httpbin.org/get")
    .uploadProgress
    { progress in
        print("上傳進(jìn)度: \(progress.fractionCompleted)")
    }
    .downloadProgress
    { progress in
        print("下載進(jìn)度: \(progress.fractionCompleted)")
    }
    .responseJSON
    { response in
        debugPrint(response)
    }

輸出結(jié)果為:

下載進(jìn)度: 1.0

[Result]: success({
    args =     {
    };
    headers =     {
        Accept = "*/*";
        "Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
        "Accept-Language" = "en;q=1.0";
        Host = "httpbin.org";
        "User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
        "X-Amzn-Trace-Id" = "Root=1-600f8c8f-1550b7515a19e35b7f534f0e";
    };
    origin = "218.104.139.115";
    url = "https://httpbin.org/get";
})
對(duì)于上傳進(jìn)度,可以通過(guò)以下方式確定進(jìn)度
  • 通過(guò)作為上傳 body 提供給 UploadRequestData 對(duì)象的長(zhǎng)度春锋。
  • 通過(guò)作為 UploadRequest 的上傳 body 提供的磁盤(pán)上文件的長(zhǎng)度矫膨。
  • 通過(guò)根據(jù)請(qǐng)求的 Content-Length header 的值(如果已手動(dòng)設(shè)置)。

f看疙、調(diào)整和重試請(qǐng)求
重定向

AlamofireRedirectHandler 協(xié)議提供了對(duì) Request 的重定向處理的控制和定制豆拨。除了每個(gè) Session RedirectHandler 之外,每個(gè) Request 都可以被賦予屬于自己的 RedirectHandler能庆,并且這個(gè) handler 將重寫(xiě) Session 提供的任何 RedirectHandler施禾。

let redirector = Redirector(behavior: .follow)
AF.request("https://httpbin.org/get")
    .redirect(using: redirector)
    .responseDecodable(of: String.self)
    { response in
        debugPrint(response)
    }
重試

AlamofireRequestInterceptor 協(xié)議由 RequestAdapterRequestRetrier 協(xié)議組成。在身份驗(yàn)證系統(tǒng)中搁胆,向每個(gè) Request 添加一個(gè)常用的 headers弥搞,并在授權(quán)過(guò)期時(shí)重試 Request邮绿。Alamofire 還包含一個(gè)內(nèi)置的 RetryPolicy 類(lèi)型,當(dāng)由于各種常見(jiàn)的網(wǎng)絡(luò)錯(cuò)誤而導(dǎo)致請(qǐng)求失敗時(shí)攀例,可以輕松重試船逮。

RequestAdapter 協(xié)議允許在通過(guò)網(wǎng)絡(luò)發(fā)出之前檢查和修改 Session 執(zhí)行的每個(gè) URLRequest。適配器的一個(gè)常見(jiàn)的用途粤铭,是在身份驗(yàn)證后面為請(qǐng)求添加 Authorization header 挖胃。RequestRetrier 協(xié)議允許重試在執(zhí)行時(shí)遇到錯(cuò)誤的請(qǐng)求。


3梆惯、Security

在與服務(wù)器和 web 服務(wù)通信時(shí)使用安全的 HTTPS 連接是保護(hù)敏感數(shù)據(jù)的重要步驟酱鸭。默認(rèn)情況下,Alamofire 接收與 URLSession 相同的自動(dòng) TLS 證書(shū)和證書(shū)鏈驗(yàn)證垛吗。雖然這保證了證書(shū)鏈的有效性凹髓,但并不能防止中間人(MITM)攻擊或其他潛在的漏洞。為了減輕 MITM 攻擊怯屉,處理敏感客戶數(shù)據(jù)或財(cái)務(wù)信息的應(yīng)用程序應(yīng)使用 AlamofireServerTrustEvaluating 協(xié)議提供的證書(shū)或公鑰固定蔚舀。

a、評(píng)估服務(wù)器信任
ServerTrustEvaluting 協(xié)議
  • DefaultTrustEvaluator:使用默認(rèn)服務(wù)器信任評(píng)估锨络,同時(shí)允許您控制是否驗(yàn)證質(zhì)詢提供的主機(jī)赌躺。
  • RevocationTrustEvaluator:檢查接收到的證書(shū)的狀態(tài)以確保它沒(méi)有被吊銷(xiāo)。這通常不會(huì)在每個(gè)請(qǐng)求上執(zhí)行羡儿,因?yàn)樗枰W(wǎng)絡(luò)請(qǐng)求開(kāi)銷(xiāo)寿谴。
  • PinnedCertificatesTrustEvaluator:使用提供的證書(shū)驗(yàn)證服務(wù)器信任。如果某個(gè)固定證書(shū)與某個(gè)服務(wù)器證書(shū)匹配失受,則認(rèn)為服務(wù)器信任有效。此評(píng)估器還可以接受自簽名證書(shū)咏瑟。
  • PublicKeysTrustEvaluator:使用提供的公鑰驗(yàn)證服務(wù)器信任拂到。如果某個(gè)固定公鑰與某個(gè)服務(wù)器證書(shū)公鑰匹配,則認(rèn)為服務(wù)器信任有效码泞。
  • CompositeTrustEvaluator:評(píng)估一個(gè) ServerTrustEvaluating 值數(shù)組兄旬,只有在所有數(shù)組中值都成功時(shí)才成功。此類(lèi)型可用于組合余寥,例如领铐,RevocationTrustEvaluatorPinnedCertificatesTrustEvaluator
  • DisabledEvaluator:此評(píng)估器應(yīng)僅在調(diào)試方案中使用宋舷,因?yàn)樗盟星笾敌髂欤@些求值又將始終認(rèn)為任何服務(wù)器信任都是有效的。此評(píng)估器不應(yīng)在生產(chǎn)環(huán)境中使用祝蝠!
// 此方法提供從底層 URLSession 接收的 SecTrust 值和主機(jī) String音诈,并提供執(zhí)行各種評(píng)估的機(jī)會(huì)
func evaluate(_ trust: SecTrust, forHost host: String) throws
ServerTrustManager
  • cert.example.com:將始終在啟用默認(rèn)和主機(jī)驗(yàn)證的情況下使用證書(shū)固定
  • keys.example.com:將始終在啟用默認(rèn)和主機(jī)驗(yàn)證的情況下使用公鑰固定
let evaluators: [String: ServerTrustEvaluating] = 
[
    // 默認(rèn)情況下幻碱,包含在 app bundle 的證書(shū)會(huì)自動(dòng)固定。
    "cert.example.com": PinnedCertificatesTrustEvalutor(),
    // 默認(rèn)情況下细溅,包含在 app bundle 的來(lái)自證書(shū)的公鑰會(huì)被自動(dòng)使用褥傍。
    "keys.example.com": PublicKeysTrustEvalutor(),
]

let manager = ServerTrustManager(evaluators: serverTrustPolicies)

b、Logging

EventMonitor 協(xié)議的最大用途是實(shí)現(xiàn)相關(guān)事件的日志記錄喇聊。

final class Logger: EventMonitor
{
    let queue = DispatchQueue(label: "xiejiapei")

    // Event called when any type of Request is resumed.
    func requestDidResume(_ request: Request)
    {
        print("Resuming: \(request)")
    }

    // Event called whenever a DataRequest has parsed a response.
    func request<Value>(_ request: DataRequest, didParseResponse response: DataResponse<Value, AFError>)
    {
        debugPrint("Finished: \(response)")
    }
}

let logger = Logger()
let session = Session(eventMonitors: [logger])
session.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}

輸出結(jié)果為:

[Request]: GET https://httpbin.org/get
    [Headers]: None
    [Body]: None
[Response]: None
[Network Duration]: None
[Serialization Duration]: 4.879705375060439e-05s
[Result]: failure(Alamofire.AFError.sessionDeinitialized)
Resuming: GET https://httpbin.org/get
"Finished: failure(Alamofire.AFError.sessionDeinitialized)"

Demo

Demo在我的Github上恍风,歡迎下載。
UseFrameworkDemo

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載誓篱,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者朋贬。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市燕鸽,隨后出現(xiàn)的幾起案子兄世,更是在濱河造成了極大的恐慌,老刑警劉巖啊研,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件御滩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡党远,警方通過(guò)查閱死者的電腦和手機(jī)削解,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)沟娱,“玉大人氛驮,你說(shuō)我怎么就攤上這事〖盟疲” “怎么了矫废?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)砰蠢。 經(jīng)常有香客問(wèn)我蓖扑,道長(zhǎng),這世上最難降的妖魔是什么台舱? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任律杠,我火速辦了婚禮,結(jié)果婚禮上竞惋,老公的妹妹穿的比我還像新娘柜去。我一直安慰自己,他們只是感情好拆宛,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布嗓奢。 她就那樣靜靜地躺著,像睡著了一般浑厚。 火紅的嫁衣襯著肌膚如雪蔓罚。 梳的紋絲不亂的頭發(fā)上椿肩,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音豺谈,去河邊找鬼郑象。 笑死,一個(gè)胖子當(dāng)著我的面吹牛茬末,可吹牛的內(nèi)容都是我干的厂榛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼丽惭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼击奶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起责掏,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤柜砾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后换衬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體痰驱,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年瞳浦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了担映。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叫潦,死狀恐怖蝇完,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情矗蕊,我是刑警寧澤短蜕,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站傻咖,受9級(jí)特大地震影響忿危,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜没龙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缎玫。 院中可真熱鬧硬纤,春花似錦、人聲如沸赃磨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)邻辉。三九已至溪王,卻和暖如春腮鞍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背莹菱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工移国, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人道伟。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓迹缀,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親凰盔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子轰绵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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