本文鏈接:https://blog.csdn.net/Hello_Hwc/article/details/72853786

前言:
Alamofire是一個由Swift編寫的優(yōu)雅的網(wǎng)絡(luò)開發(fā)框架。
大部分用Swift編寫的iOS App的網(wǎng)絡(luò)模塊都是基于Alamofire的。作為Swift社區(qū)最活躍的幾個項目之一,有許多開發(fā)者在不斷的對其進行完善,所以學(xué)習這種優(yōu)秀的開源代碼對深入理解Swift的特性很有幫助。
本文很長,大到整個框架的設(shè)計,小到某些基礎(chǔ)功能的使用都會涉及。
URL Loading System
iOS的網(wǎng)絡(luò)開發(fā)(URL Loading System)的類層次如下:

從圖中可以看出,整個框架包括URL Loading相關(guān)的核心類和五種輔助類。其中,五種輔助類劃分如下
Configuration 配置信息,比如Cookie的存儲策略,TLS版本等等。
Authentication and Credentials 授權(quán)和證書
Protocol support 用做proxy來攔截或特殊處理某些URL
Cookie Storage 管理Cookie
Cache Management 管理緩存
Alamofire就是建立在NSURLSession上的封裝。
NSURLSession是在2013年推出的新API,并且Apple在2015年廢棄了NSURLConnection。如果你的App還在用以NSURLConnection建立的網(wǎng)絡(luò)層(比如AFNetworking 2.x),那么你真的應(yīng)該考慮升級到NSURLSession(比如AFNetworking 3.x),廢棄的API也許還能正常工作,但是Apple已對其不再維護,當然也就不支持HTTP 2.0等新特性。
關(guān)于NSURLSesson的基礎(chǔ)使用,我之前有過幾篇博客,可以在這個鏈接找到:
博客iOS網(wǎng)絡(luò)開發(fā)分類
那么,用NSURLSession來進行HTTP/HTTPS請求的時候,實際的過程如何呢?

建立NSURLSessionTask,并且resume.
檢查cache策略,如果有需要從本地cache中直接返回數(shù)據(jù)
通過DNS進行域名查找
建立TCP連接
如果是HTTPS,進行TLS握手(如有資源需要認證訪問,可能需要客戶端提供證書,用戶名密碼等信息)
請求開始,收到HTTP的Response
接收HTTP的Data
Tips: 理解HTTP/HTTPS的請求過程很重要,因為往往你需要統(tǒng)計API請求在哪個階段出了問題,然后對癥下藥,提高用戶體驗。
整體架構(gòu)
Alamofie的整體功能圖如下:

其中
左側(cè)是暴露給外部的接口,右側(cè)是內(nèi)部實現(xiàn)相關(guān)
這三個模塊比較獨立:AlamofireImage 和 AlamofireNetworkActivityIndicator 是基于Alamofire開發(fā)的獨立的庫,分別用來做圖片和網(wǎng)絡(luò)狀態(tài)小菊花,NetworkReachabilityManager也是先對獨立的用來檢測蜂窩移動,WIFI等網(wǎng)絡(luò)變化的。
我們先從一個API調(diào)用切入,來分析各個模塊的作用:
Alamofire.request(/**/).validate(/**/).responseJSON {/**/}
初始化SessionManager的單例default
//整理后代碼
self.delegate = SessionDelegate()
self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
在初始化SessionManager代碼里,提供了一個默認的SessionDelegate,并且初始化了一個URLSession,這個URLSession的delegate是SessionDelegate。
通過這個初始化,我們知道URLSession的幾個代理事件都是傳遞給SessionManager的SessionDelegate了。
執(zhí)行全局方法Alamofire.request
方法體中調(diào)用SessionManager.default單例的實例方法來創(chuàng)建DataRequest。這一步做了如下動作:
根據(jù)傳入的url,parameters等參數(shù)創(chuàng)建URLRequest
根據(jù)URLRequest和SessionManager的屬性session(URLSession),adapter(請求適配器),queue(GCD queue)創(chuàng)建URLSessionDataTask
根據(jù)基類Request提供的方法,創(chuàng)建子類DataRequest實例,并且為子類DataRequest初始化一個DataTaskDelegate。
每一個DataRequest對應(yīng)一個DataTaskDelegate,每一個TaskDelegate有一個OperationQueue,這個queue在初始化的時候是掛起狀態(tài)的,并且是一個串行隊列(maxConcurrentOperationCount = 1)。
open class 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
//省略
}
delegate.error = error
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() } //加入統(tǒng)計請求結(jié)束的Operation
}
}
按需執(zhí)行DataTask的resume方法
執(zhí)行DataTask.validate
內(nèi)容很簡單,就是把傳入的閉包保存起來,等待后續(xù)執(zhí)行,并且返回Self
執(zhí)行DataTask.responseJSON
在這個方法里,創(chuàng)建一個NSOperation加入到DataTaskDelegate的queue中,這個queue在創(chuàng)建之初是刮掛起狀態(tài)的,所以提交的任務(wù)不會執(zhí)行。
URLSession收到數(shù)據(jù)
首先SessionDelegate代理方法被調(diào)用:
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if let dataTaskDidReceiveData = dataTaskDidReceiveData {//有自定義實現(xiàn)
dataTaskDidReceiveData(session, dataTask, data)
} else if let delegate = self[dataTask]?.delegate as? DataTaskDelegate {//走默認實現(xiàn)
delegate.urlSession(session, dataTask: dataTask, didReceive: data)
}
}
在這個代理方法里,根據(jù)存儲的字典 URLSessionTask -> TaskDelegate 找到這個task的DataTaskDelegate,調(diào)用其方法
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
//整理后代碼
mutableData.append(data) //存儲數(shù)據(jù)到內(nèi)存
progressHandler.queue.async { progressHandler.closure(self.progress) } //回調(diào)progressHandler
}
URLSession完成Task
首先調(diào)用SessionDelegate中的URLSession的代理方法
open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
//整理后代碼
//執(zhí)行response的validation
request.validations.forEach { $0() }
//喚起queue,來執(zhí)行提交的任務(wù)
strongSelf[task]?.delegate.queue.isSuspended = false
strongSelf[task] = nil
}
由于queue被喚起,所以之前提交的完成callback會被執(zhí)行。
執(zhí)行網(wǎng)絡(luò)請求完成的callback
//序列化請求結(jié)果,這里的responseSerializer為DataResponseSerializerProtocol協(xié)議類型
let result = responseSerializer.serializeResponse(
self.request,
self.response,
self.delegate.data,
self.delegate.error
)
//建立Response對象
var dataResponse = DataResponse<T.SerializedObject>(
request: self.request,
response: self.response,
data: self.delegate.data,
result: result,
timeline: self.timeline
)
//增加統(tǒng)計相關(guān)信息
dataResponse.add(self.delegate.metrics)
//執(zhí)行傳入的必報,也就是responseJSON函數(shù)傳入的閉包
(queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
API設(shè)計
衡量一個框架好壞最重要的因素就是是否容易使用。
那么,如何定義容易使用呢?
根據(jù)二八原則,對于一個框架的使用百分之八十的時候都是很基礎(chǔ)的功能使用,當這些基礎(chǔ)的功能使用是容易的,我們認為這個框架是容易使用的。
我們來對比一下,同樣GET一個URL,然后把數(shù)據(jù)解析成JSON。使用NSURLSession層次的API如下
guard let url = URL(string: "https://raw.githubusercontent.com/LeoMobileDeveloper/React-Native-Files/master/person.json") else {
return;
}
let dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else{
return;
}
do{
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(json)
}catch let error{
print(error)
}
};
dataTask.resume()
使用Alamofire
Alamofire.request("https://raw.githubusercontent.com/LeoMobileDeveloper/React-Native-Files/master/person.json").responseJSON { (response) in
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
Tips: 這里的Alamofire.request指的是module(模塊) Alamofire的一個全局方法request調(diào)用。
可以看到,使用系統(tǒng)的API,我們不得不先創(chuàng)建URL,然后建立DataTask,并且Resume。接著在callback里去解析JSON。由于Swift是一種強類型的語言,我們不得不進行大量的邏輯判斷和try-catch。
而Alamofire把這些步驟簡化成了一個靜態(tài)的方法調(diào)用,并且用鏈式的方式來處理異步的Response解析。由于是鏈式的,你可以用鏈式的方式實現(xiàn)很多邏輯,比如驗證返回值:
Alamofire.request("https://httpbin.org/get")
.validate(statusCode: 200..<300) //返回值驗證
.responseData { response in //解析返回的數(shù)據(jù)
switch response.result {
case .success:
print("Validation Successful")
case .failure(let error):
print(error)
}
}
用鏈式的方式進行異步處理是一個很好的實踐,延伸閱讀可以參考:PromiseKit,RxSwift。
鏈式的異步處理有很多優(yōu)點:
優(yōu)雅的處理大量的callback
代碼更容易理解,更容易維護
不需要在每一步都進行錯誤檢查
80%情況下的API調(diào)用
Alamofire是采用靜態(tài)方法的方式來提供80%情況下的API,這些全局方法可以在Alamofire.swift找到,以request為例:
@discardableResult //關(guān)鍵詞告訴編譯器,即使返回值不被持有,也別報警告
public func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest
{
return SessionManager.default.request(
url,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers
)
}
我們來分析下這個簡單卻又精煉的方法,方法的幾個參數(shù)
url 請求的URL,協(xié)議URLConvertible類型(Alamofire用extension的方式為URL,String,URLComponents實現(xiàn)了這個協(xié)議)
method 請求的HTTP方法,默認為GET
parameters 請求的參數(shù),默認為nil
encoding,,參數(shù)編碼類型,默認URLEncoding.default,也就是根據(jù)HTTP方法的類型決定參數(shù)是query或者body里
headers, HTTP Header
返回值是一個DataRequest實例,這個實例就是異步調(diào)用鏈的頭部。
Tips: 用默認參數(shù)來實現(xiàn)默認配置是一個很好的實踐。
如何實現(xiàn)鏈式調(diào)用
open class Request {
var validations: [() -> Void] = []
public func validate(_ validation: @escaping Validation) -> Self {
let validationExecution: () -> Void = {/**/}
validations.append(validationExecution)
return self
}
}
從代碼中,我們可以比較清楚的看出鏈式調(diào)用的原理:
函數(shù)的參數(shù)是閉包類型,方法體把這個閉包類型輸入存儲起來,并且返回Self。在合適的時候執(zhí)行閉包即可實現(xiàn)異步的鏈式調(diào)用。
模塊功能
軟件設(shè)計有一個非常重要的原則就是:單一功能原則。
Alaofire的文件劃分如下:

我們來分析Alamofire的各個模塊負責的功能:
SessionManager 整個Alamofire框架的核心樞紐,封裝了URLSession。負責提供外部調(diào)用的API,處理請求適配器,請求的重拾。
SessionDelegate SessionManager的代理,封裝了URLSessionDelegate。負責對Task的回調(diào)事件提供默認實現(xiàn)(轉(zhuǎn)發(fā)給TaskDelegate進行實際處理),并且以閉包的方式暴露給外部,讓外部可以自定義實現(xiàn)。
TaskDelegate 對URLSessionTask的回調(diào)進行實際的處理,并且執(zhí)行task的完成回調(diào)用
Request 是URLSessionTask的封裝,是暴露給上層的請求任務(wù)
ParameterEncoding 對參數(shù)進行Encoding(JSON,query等)
Response 代表返回數(shù)據(jù)序列化后的結(jié)果
ResponseSerializer 對返回的數(shù)據(jù)進行序列化(JSON,property list等)
ServerTrustPolicyManager/ServerTrustPolicy 對TLS等過程中發(fā)生的認證進行處理
Timeline 純粹的用來進行網(wǎng)絡(luò)請求過程的數(shù)據(jù)統(tǒng)計
線程
Alamofire的線程處理都是采用GCD和NSOperation,并沒有使用底層的Thread。
SessionManager
每一個SessionManager有一個常量屬性
let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)
1
這個queue用來做task的初始化工作,也做了比如文件創(chuàng)建等
//task初始化
return task = queue.sync { session.downloadTask(with: urlRequest) }
//創(chuàng)建目錄
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
URLSession
Session是這樣被初始化的:
self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
1
delegateQueue是URLSession的各種回調(diào)函數(shù)被調(diào)用的串行隊列,這里傳入nil,表示由系統(tǒng)自動為我們創(chuàng)建回調(diào)隊列。
GlobalQueue
關(guān)于全局隊列,有如下使用
//重試
DispatchQueue.utility.after{}
//初始化上傳的MultipartFormData
DispatchQueue.global(qos: .utility).async
TaskDelegate
每一個Task有一個TaskDelegate,每一個TaskDelegate有一個常量屬性queue
self.queue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
operationQueue.qualityOfService = .utility
return operationQueue
}()
這個queue有一點黑科技,在創(chuàng)建的時候是掛起的,然后不斷的往里塞任務(wù):比如responseJSON等。然后等Task完成的時候,再喚起queue,執(zhí)行這些任務(wù)。
還是舉一個例子,我們來看看隊列之前的切換:
Alamofire.request(//).validate(//).responseJSON {/**/}
1
主隊列調(diào)用request方法
sync到SessionManager的queue上創(chuàng)建URLSessionDataTask
主隊列調(diào)用validate方法和responseJSON保存相關(guān)閉包
URLSession中由系統(tǒng)自動創(chuàng)建的queue收到delegate事件回調(diào)
收到URLSessionTask完成的回調(diào),TaskDelegate的queue被喚起
異步到主隊列執(zhí)行responseJSON中傳入的閉包
當然,上述的隊列使用不包括以參數(shù)方式傳遞進入的,比如responseJSON,就可以指定這個閉包執(zhí)行的隊列
public func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (DataResponse<Any>) -> Void)
-> Self{}
錯誤處理
數(shù)據(jù)結(jié)構(gòu)
AlamoFire的錯誤處理是采用了帶關(guān)聯(lián)值枚舉,在Swift開發(fā)中,枚舉是最常見的用來處理錯誤的。
在關(guān)聯(lián)值枚舉中,Alamofire還定義了內(nèi)部類型,來對錯誤類型進行二次分類。代碼如下:
public enum AFError: Error {
public enum ParameterEncodingFailureReason {/*省略*/}
public enum MultipartEncodingFailureReason {/*省略*/}
public enum ResponseValidationFailureReason {/*省略*/}
public enum ResponseSerializationFailureReason {/*省略*/}
//枚舉的可能值
case invalidURL(url: URLConvertible)
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
case responseValidationFailed(reason: ResponseValidationFailureReason)
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}
我們來分析為什么要這樣定義這些錯誤類型,一個典型的網(wǎng)絡(luò)庫的請求數(shù)據(jù)流如下:
其中,在調(diào)用URLSession相關(guān)的API之前,我們要先創(chuàng)建URLRequest,然后交給URLSession去做實際的HTTP請求,然后拿到HTTP請求的二進制數(shù)據(jù),根據(jù)需要轉(zhuǎn)換成字符串/JSON等交給上層。
所以,Alomofire的錯誤處理思想是:
根據(jù)錯誤發(fā)生的位置進行一級分類,再用嵌套類型對錯誤進行二次分類。
除了錯誤定義之外,開發(fā)者抓到錯誤能有友善的描述信息也是很重要的,這就是。Swift提供LocalizedError
extension AFError: LocalizedError {
public var errorDescription: String? {
/*省略*/
}
}
extension AFError.ParameterEncodingFailureReason {
var localizedDescription: String {
/*省略*/
}
}
Swift錯誤處理延伸閱讀: 詳解Swift中的錯誤處理
繼承
NRULSessionTask是由繼承來實現(xiàn)的,繼承關(guān)系如下
URLSessionTask — Task的基類
URLSessionDataTask - 拉取URL的內(nèi)容NSData
URLSessionUploadTask — 上傳數(shù)據(jù)到URL,并且返回是NSData
URLSessionDownloadTask - 下載URL的內(nèi)容到文件
URLSessionStreamTask — 建立TCP/IP連接
仿照這種關(guān)系,Alamofire的Request也是類似的繼承關(guān)系:
Request — Task的基類
DataRequest - 拉取URL的內(nèi)容NSData
UploadRequest — 上傳數(shù)據(jù)到URL,并且返回是NSData
DownloadRequest - 下載URL的內(nèi)容到文件
StreamRequest — 建立TCP/IP連接
其實原因和很簡單:父類提供基礎(chǔ)的屬性和方法來給子類復(fù)用。
在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
/**/
}
}
協(xié)議
Swift是面向協(xié)議編程的語言。
Alamofire的很多設(shè)計都是以協(xié)議為中心的,

以ParameterEncoding協(xié)議:
定義如下:
public protocol ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}
接口依賴于這個協(xié)議類型
public func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest
{
/*略*/
}
這樣在傳入的時候,只要是這個協(xié)議類型都可以,不管是struct,enum或者class。
Alamofire實現(xiàn)了三種Encoding方式:
public struct URLEncoding: ParameterEncoding {}
public struct JSONEncoding: ParameterEncoding {}
public struct PropertyListEncoding: ParameterEncoding {}
擴展性
由于提供的接口是協(xié)議類型的,于是你可以方便直接把一個實例當作url,并且自定義encodeing方法
enum API:URLConvertible{
case login
public func asURL() throws -> URL {
//Return login URL
}
}
class CustomEncoding: ParameterEncoding{/*/*}
然后,你就可以這么調(diào)用了
Alamofire.request(API.login, method: .post, encoding: CusomEncoding())
可以看到,使用協(xié)議提供的接口是抽象的接口,與具體的class/enum/struct無關(guān),也就易于擴展
代理
代理是CocoaTouch一個很優(yōu)秀的設(shè)計模式,它提供了一種盲通信的方式把相關(guān)的任務(wù)劃分到不同的類中。
在Alamofire中,最主要的就是這兩對代理關(guān)系:

由于Delegate的存在,
SessionManager只需要關(guān)注URLSession的封裝即可,session層面的事件回調(diào)交給由SessionDelegate處理
Request只需要關(guān)注URLSessionTask的封裝,task層面的任務(wù)交給RequestDelegate處理。
這樣,保證了各個模塊之間的功能單一,不會互相耦合。
類型安全
Swift本身是一種類型安全的語言,這意味著如果編譯器發(fā)現(xiàn)類型不對,你的代碼將編譯不通過。
URLRequest有一個屬性是HTTPMethod
var httpMethod: String? { get set }
它的類型是String類型,這意味著你可以隨意的賦值,編譯器缺不會提示你你的輸入可能又問題。
request.httpMethod = "1234"
考慮到HTTPMethod無非也就是那幾種,很適合用enum來做,Alamofire對其進行了封裝
public enum HTTPMethod: String {
case options = "OPTIONS"
case get = "GET"
case head = "HEAD"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
case trace = "TRACE"
case connect = "CONNECT"
}
然后,上層的方法提供的接口是枚舉類型:
public func request(
_ url: URLConvertible,
method: HTTPMethod = .get, //這里
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest
{
/*略*/
}
這樣,編譯器就能夠進行合理的檢查,也不容易出錯了。
版本與平臺適配
Alamofire適配的平臺有ios/osx/tvos/watchos,適配的最低iOS版本是iOS 8。 那么,就出現(xiàn)了一個問題
有些平臺沒有對應(yīng)的API
有些API是高版本的系統(tǒng)才有的
舉個例子:
func streamTask(with service: NetService) -> URLSessionStreamTask
Alamofire采用如下方式進行適配:
@avialable - 用來標記適配系統(tǒng)版本(for編譯器)
比如,這個函數(shù)被標記為iOS 9.0后可用,如果直接在target iOS 8的調(diào)用,則會報錯??梢栽趇f #available{}中調(diào)用
@discardableResult
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
public func stream(withHostName hostName: String, port: Int) -> StreamRequest {
return SessionManager.default.stream(withHostName: hostName, port: port)
}
if ... #endif - 用作條件編譯(for編譯器)
例如:在watchOS上不編譯
#if !os(watchOS)
@discardableResult
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
public func stream(withHostName hostName: String, port: Int) -> StreamRequest {
return SessionManager.default.stream(withHostName: hostName, port: port)
}
#endif
available - 滿足平臺和系統(tǒng)要求才調(diào)用(for 編譯器,運行時)
extension Response {
mutating func add(_ metrics: AnyObject?) {
#if !os(watchOS)
guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) else { return }
guard let metrics = metrics as? URLSessionTaskMetrics else { return }
_metrics = metrics
#endif
}
}
總結(jié)
Alamofire是一個優(yōu)雅的Swift開源庫,它的代碼真的很優(yōu)雅,強烈建議對Swift感興趣并且想深入學(xué)習的同學(xué)用幾天的空余時間去研究下。看的時候多問自己幾個問題: