注:本文始發于個人 GitHub 項目 ShannonChenCHN/iOSDevLevelingUp。
源碼倉庫地址:https://github.com/AFNetworking/AFNetworking
AFNetworking 作為我們最基礎的網絡框架,目前在 GitHub 上 Objective-C 語言類排名第一,幾乎每個涉及到網絡請求的 APP 都會用到,其重要性可見一斑。再者,作為 iOS 開發領域最受歡迎的開源項目,其中凝聚了眾多大神的智慧,無論是在技術點上,還是架構設計上、問題處理方式上,都具有很高的學習價值。
這兩天正好趁著假期有空,可以跟著前人總結的一些精華,仔細研讀一下這個優秀的網絡框架的實現。站在巨人的肩膀上,才能看得遠。
這篇文章先從整體架構開始,再從實際使用案例入手,梳理一下核心邏輯,然后再依次了解下各個具體模塊的實現,最后再回顧一下 2.x 版本的實現,總結一下 AFNetworking 的價值。
注:這篇文章不會逐行分析源碼,具體的代碼注釋見 這里。
目錄
- 一、架構
- 二、核心邏輯
- 三、AFURLSessionManager
- 3.1 線程
- 3.2 AFURLSessionManagerTaskDelegate
- 3.3 NSProgress
- 3.4 NSSecureCoding
- 3.5 _AFURLSessionTaskSwizzling
- 四、AFURLRequestSerialization
- 4.1 構建普通請求
- 4.2 構建 multipart 請求
- 五、AFURLResponseSerialization
- 5.1
-validateResponse:data:error:
方法 - 5.2
-responseObjectForResponse:data:error:
方法
- 5.1
- 六、AFSecurityPolicy
- 6.1 預備知識點
- 6.1.1 為什么要使用 HTTPS
- 6.1.2 HTTPS 的出現
- 6.1.3 SSL/TLS 協議
- 6.1.4 HTTPS 與 HTTP 的區別是什么?
- 6.1.5 HTTPS 連接的建立過程
- 6.1.6 HTTPS 傳輸時是如何驗證證書的?怎樣應對中間人偽造證書?
- 6.1.7 Certificate Pinning 是什么?
- 6.2 AFSecurityPolicy 的實現
- 6.1 預備知識點
- 七、AFNetworkReachabilityManager
- 八、UIKit 擴展
- 九、AFNetworking 2.x
- 十、AFNetworking 的價值
- 10.1 請求調度:NSURLConnection + NSOperation
- 10.2 更高層次的抽象
- 10.3 block
- 10.4 模塊化
- 十一、問題
- 十二、收獲
一、架構
AFNetworking 一共分為 5 個模塊,2 個核心模塊和 3 個輔助模塊:
- Core
- NSURLSession(網絡通信模塊)
- AFURLSessionManager(封裝 NSURLSession)
- AFHTTPSessionManager(繼承自 AFURLSessionManager,實現了 HTTP 請求相關的配置)
- Serialization
- AFURLRequestSerialization(請求參數序列化)
- AFHTTPRequestSerializer
- AFJSONRequestSerializer
- AFPropertyListRequestSerializer
- AFURLResponseSerialization(驗證返回數據和反序列化)
- AFHTTPResponseSerializer
- AFJSONResponseSerializer
- AFXMLParserResponseSerializer
- AFXMLDocumentResponseSerializer (Mac OS X)
- AFPropertyListResponseSerializer
- AFImageResponseSerializer
- AFCompoundResponseSerializer
- AFURLRequestSerialization(請求參數序列化)
- NSURLSession(網絡通信模塊)
- Additional Functionality
- Security(網絡通信安全策略模塊)
- Reachability(網絡狀態監聽模塊)
- UIKit(對 iOS 系統 UI 控件的擴展)
<div align="center">圖 1 AFNetworking 整體架構</div>
二、核心邏輯
先來看一下如何使用 AFNetworking 發送一個 GET 請求:
NSURL *url = [[NSURL alloc] initWithString:@"https://news-at.zhihu.com"];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:url];
[manager GET:@"api/4/news/latest" parameters:nil progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@" ,responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@", error);
}];
首先使用一個 URL,通過調用 -initWithBaseURL:
方法創建了一個 AFHTTPSessionManager 的實例,然后再調用 -GET:parameters:progress:success:failure:
方法發起請求。
-initWithBaseURL:
方法的調用棧如下:
- [AFHTTPSessionManager initWithBaseURL:]
- [AFHTTPSessionManager initWithBaseURL:sessionConfiguration:]
- [AFURLSessionManager initWithSessionConfiguration:]
- [NSURLSession sessionWithConfiguration:delegate:delegateQueue:]
- [AFJSONResponseSerializer serializer] // 負責序列化響應
- [AFSecurityPolicy defaultPolicy] // 負責身份認證
- [AFNetworkReachabilityManager sharedManager] // 查看網絡連接情況
- [AFHTTPRequestSerializer serializer] // 負責序列化請求
- [AFJSONResponseSerializer serializer] // 負責序列化響應
AFURLSessionManager 是 AFHTTPSessionManager 的父類,
AFURLSessionManager 負責創建和管理 NSURLSession 的實例,管理 AFSecurityPolicy 和初始化 AFNetworkReachabilityManager,來保證請求的安全和查看網絡連接情況,它有一個 AFJSONResponseSerializer 的實例來序列化 HTTP 響應。
AFHTTPSessionManager 有著自己的 AFHTTPRequestSerializer 和 AFJSONResponseSerializer 來管理請求和響應的序列化,同時依賴父類實現發出 HTTP 請求、管理 Session 這一核心功能。
-GET:parameters:progress:success:failure:
方法的調用棧:
- [AFHTTPSessionManager GET:parameters:process:success:failure:]
- [AFHTTPSessionManager dataTaskWithHTTPMethod:parameters:uploadProgress:downloadProgress:success:failure:] // 返回一個 NSURLSessionDataTask 對象
- [AFHTTPRequestSerializer requestWithMethod:URLString:parameters:error:] // 返回 NSMutableURLRequest
- [AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:] 返回一個 NSURLSessionDataTask 對象
- [NSURLSession dataTaskWithRequest:] 返回一個 NSURLSessionDataTask 對象
- [AFURLSessionManager addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler:]
- [AFURLSessionManagerTaskDelegate init]
- [AFURLSessionManager setDelegate:forTask:] // 為每個 task 創建一個對應的 delegate
- [NSURLSessionDataTask resume]
發送請求的核心在于創建和啟動一個 data task,AFHTTPSessionManager 只是提供了 HTTP 請求的接口,內部最終還是調用了父類 AFURLSessionManager 來創建 data task(其實也就是通過 NSURLSession 創建的 task),AFURLSessionManager 中會為每個 task 創建一個對應的 AFURLSessionManagerTaskDelegate 對象,用來處理回調。
在請求發起時有一個序列化的工具類 AFHTTPRequestSerializer 來處理請求參數。
請求回調時的方法調用棧:
- [AFURLSessionManager URLSession:task:didCompleteWithError:]
- [AFURLSessionManagerTaskDelegate URLSession:task:didCompleteWithError:]
- [AFJSONResponseSerializer responseObjectForResponse:data:error:] // 解析 JSON 數據
- [AFHTTPResponseSerializer validateResponse:data:] // 驗證數據
- [AFURLSessionManagerTaskDelegate URLSession:task:didCompleteWithError:]_block_invoke_2.150
- [AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:]_block_invoke
AFURLSessionManager 在代理方法中收到服務器返回數據的后,會交給 AFURLSessionManagerTaskDelegate 去處理,接著就是用 AFJSONResponseSerializer 去驗證和解析 JSON 數據,最后再通過 block 回調的方式返回最終結果。
三、AFURLSessionManager
AFURLSessionManager 是 AFHTTPSessionManager 的父類,主要有以下幾個功能:
- 負責創建和管理 NSURLSession
- 管理 NSURLSessionTask
- 實現 NSURLSessionDelegate 等協議中的代理方法
- 使用 AFURLSessionManagerTaskDelegate 管理上傳、下載進度,以及請求完成的回調
- 將整個請求流程相關的組件串聯起來
- 負責整個請求過程的線程調度
- 使用 AFSecurityPolicy 驗證 HTTPS 請求的證書
1. 線程
一般調用 AFNetworking 的請求 API 時,都是在主線程,也是主隊列。然后直到調用 NSURLSession 的 -resume
方法,一直都是在主線程。
在 AFURLSessionManager 的初始化方法中,設置了 NSURLSession 代理回調線程的最大并發數為 1,因為就像 NSURLSession 的 -sessionWithConfiguration:delegate:delegateQueue:
方法的官方文檔中所說的那樣,所有的代理方法回調都應該在一個串行隊列中,因為只有這樣才能保證代理方法的回調順序。
NSURLSession 代理方法回調是異步的,所以收到回調時的線程模式是“異步+串行隊列”,這個時候可以理解為處于回調線程。
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
...
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1; // 代理回調線程最大并發數為 1
// 初始化 NSURLSession 對象
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
...
return self;
}
收到代理回調后,接著在 AFURLSessionManagerTaskDelegate 的 -URLSession:task:didCompleteWithError:
方法中,異步切換到 processing queue 進行數據解析,數據解析完成后再異步回到主隊列或者自定義隊列。
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
...
// 如果請求成功,則在一個 AF 的并行 queue 中,去做數據解析等后續操作
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
...
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
...
});
});
...
}
問題:
有個讓我感到困惑的地方是,這里最后回調時為什么要用dispatch_group_async
將任務放到隊列組中去執行,搜了一下也沒看到這個組中的任務執行完了要做什么,難道是要留給外面的調用方用的?
<div align="center">圖 2 AFNetworking 中的線程調度</div>
2. AFURLSessionManagerTaskDelegate
AFURLSessionManager 中幾乎實現了所有的 NSURLSession 相關的協議方法:
- NSURLSessionDelegate
- NSURLSessionTaskDelegate
- NSURLSessionDataDelegate
- NSURLSessionDownloadDelegate
但是AFURLSessionManager 中實現的這些代理方法都只是做一些非核心邏輯的處理,每個代理方法中都回調了一個自定義邏輯的 block,如果 block 被賦值了,那么就調用它。
AFURLSessionManager 把最核心的代理回調處理交給 AFURLSessionManagerTaskDelegate 類去實現了,AFURLSessionManagerTaskDelegate 可以根據對應的 task 去進行上傳、下載進度回調和請求完成的回調處理:
- URLSession:task:didCompleteWithError:
- URLSession:dataTask:didReceiveData:
- URLSession:downloadTask:didFinishDownloadingToURL:
AFURLSessionManager 通過屬性 mutableTaskDelegatesKeyedByTaskIdentifier
(一個 NSDictionary 對象)來存儲并管理每一個 NSURLSessionTask 所對應的 AFURLSessionManagerTaskDelegate,它以 taskIdentifier 為鍵存儲 task。在請求最終完成后,又將 AFURLSessionManagerTaskDelegate 移除。
<div align="center">圖 3 AFNetworking 中的代理回調邏輯</div>
3. NSProgress
AFURLSessionManagerTaskDelegate 借助了 NSProgress 這個類來實現進度的管理,NSProgress 是 iOS 7 引進的一個用來管理任務進度的類,可以表示一個任務的進度信息,我們還可以對其進行開始
暫停、取消等操作,完整的對應了 task 的各種狀態。
AFURLSessionManagerTaskDelegate 通過 KVO 監聽 task 的進度更新,來同步更新 NSProgress 的進度數據。同時,還用 KVO 監聽了 NSProgress 的 fractionCompleted 屬性的變化,用來更新最外面的進度回調 block,回調時將這個 NSProgress 對象作為參數帶過去。
另一方面,AFURLSessionManagerTaskDelegate 中還分別對下載和上傳的 NSProgress 對象設置了開始、暫停、取消等操作的 handler,將 task 跟 NSProgress 的狀態關聯起來。這樣一來,就可以通過控制 NSProgress 對象的這些操作就可以控制 task 的狀態。
延伸閱讀:
4. NSSecureCoding
AFNetworking 的大多數類都支持歸檔解檔,但實現的是 NSSecureCoding 協議,而不是 NSCoding 協議,這兩個協議的區別在于 NSSecureCoding 協議中定義的解碼的方法是 -decodeObjectOfClass:forKey:
方法,而不是 -decodeObjectForKey:
,這就要求解數據時要指定 Class。在 bang 的文章中看到說是這樣做更安全,因為序列化后的數據有可能被篡改,若不指定 Class,decode 出來的對象可能不是原來的對象,有潛在風險。(不過暫時還是沒能理解。)
5. _AFURLSessionTaskSwizzling
_AFURLSessionTaskSwizzling 的唯一作用就是將 NSURLSessionTask 的 -resume
和 -suspend
方法實現替換成自己的實現,_AFURLSessionTaskSwizzling 中這兩個方法的實現是先調用原方法,然后再發出一個通知。
_AFURLSessionTaskSwizzling 是通過在 +load
方法中進行 Method Swizzling 來實現方法交換的,由于 NSURLSessionTask 的實現是類簇,不能直接通過調用 +class
來獲取真正的類,而且在 iOS 7 和 iOS 8 下的實現不同,所以這里的 swizzling 實現起來有點復雜。具體原因見 GitHub 上的討論。
問題:
有點不明白的是,NSURLSessionTask 有三個子類:NSURLSessionDataTask、NSURLSessionDownloadTask 和 NSURLSessionUploadTask,為什么不用考慮這三個子類自己也實現了自己的-resume
和-suspend
方法的情況呢?
四、AFURLRequestSerialization
AFURLRequestSerialization 是一個抽象的協議,用于構建一個規范的 NSURLRequest。基于 AFURLRequestSerialization 協議,AFNetworking 提供了 3 中不同數據形式的序列化工具(當然你也可以自定義其他數據格式的序列化類):
- AFHTTPRequestSerializer:普通的 HTTP 請求,默認數據格式是
application/x-www-form-urlencoded
,也就是 key-value 形式的 url 編碼字符串 - AFJSONRequestSerializer:參數格式是 json
- AFPropertyListRequestSerializer:參數格式是蘋果的 plist 格式
<div align="center">圖 4 AFURLRequestSerialization 類圖</div>
AFHTTPRequestSerializer 主要實現了兩個功能:
- 構建普通請求:格式化請求參數,生成 HTTP Header。
- 構建 multipart 請求,上傳數據時會用到。
1. 構建普通請求
AFHTTPRequestSerializer 在構建普通請求時,做了以下幾件事:
- 創建 NSURLRequest
- 設置 NSURLRequest 相關屬性
- 設置 HTTP Method
- 設置 HTTP Header
- 序列化請求參數
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
// 創建請求
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method; // 設置 Method
// 這里本來是直接把 self 的一些屬性值直接傳給 request 的,但是因為初始默認情況下,
// 當前類中與 NSURLRequest 相關的那些屬性值為 0,而導致外面業務方使用 NSURLSessionConfiguration 設置屬性時失效,
// 所以通過對這些屬性添加了 KVO 監聽判斷是否有值來解決這個傳值的有效性問題
// 詳見 https://github.com/AFNetworking/AFNetworking/commit/49f2f8c9a907977ec1b3afb182404ae0a6bce883
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
// 設置 HTTP header;請求參數序列化,再添加到 query string 或者 body 中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
在設置 NSURLRequest 相關屬性時,有點繞,本來可以直接將 AFHTTPRequestSerializer 自己的屬性值傳給 NSURLRequest 對象的,但是后來改成了 KVO 的形式,主要是因為 NSURLRequest 對象有些屬性是純量數據類型(比如 timeoutInterval),在 AFHTTPRequestSerializer 初始化后,這些跟 NSURLRequest 相關的屬性值初始默認值是 0,所以是不知道外面有沒有設置過值,如果將 AFHTTPRequestSerializer 的值都傳給 NSURLRequest 對象的話,很有可能會導致 NSURLSessionConfiguration 中設置的相同屬性失效。
AFNetworking 幫我們組裝好了一些 HTTP 請求頭,包括:
-
Content-Type
,請求參數類型 -
Accept-Language
,根據[NSLocale preferredLanguages]
方法讀取本地語言,告訴服務端自己能接受的語言。 User-Agent
-
Authorization
,提供 Basic Auth 認證接口,幫我們把用戶名密碼做 base64 編碼后放入 HTTP 請求頭。
一般我們請求都會按 key=value
的方式帶上各種參數,GET 方法參數直接拼在 URL 后面,POST 方法放在 body 上,NSURLRequest 沒有封裝好這個參數的序列化,只能我們自己拼好字符串。AFHTTPRequestSerializer 提供了接口,讓參數可以是 NSDictionary, NSArray, NSSet 這些類型,再由內部解析成字符串后賦給 NSURLRequest。
參數序列化流程大概是這樣的:
- 用戶傳進來的數據,支持包含 NSArray,NSDictionary,NSSet 這三種數據結構。
- 先將每組 key-value 轉成 AFQueryStringPair 對象的形式,保存到數組中(這樣做的目的是因為最后可以根據不同的字符串編碼生成對應的 key=value 字符串)
- 然后取出數組中的 AFQueryStringPair 對象,轉成一個個 NSString 對象再保存到新數組中
- 最后再將這些
key=value
的字符串用&
符號拼接起來
@{
@"name" : @"steve",
@"phone" : @{@"mobile": @"xx", @"home": @"xx"},
@"families": @[@"father", @"mother"],
@"nums" : [NSSet setWithObjects:@"1", @"2", nil]
}
||
\/
@[
field: @"name", value: @"steve",
field: @"phone[mobile]", value: @"xx",
field: @"phone[home]", value: @"xx",
field: @"families[]", value: @"father",
field: @"families[]", value: @"mother",
field: @"nums", value: @"1",
field: @"nums", value: @"2",
]
||
\/
@[
@"name=steve", // 注:實際代碼中這里的 “=” 會被編碼
@"phone[mobile]=xx",
@"phone[home]=xx",
@"families[]=father",
@"families[]=mother",
@"nums=1",
@"nums=2"
]
||
\/
@"name=steve&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&nums=2"
請求參數序列化完成后,再根據不同的 HTTP 請求方法分別處理,對于 GET/HEAD/DELETE 方法,把參數直接加到 URL 后面,對于其他如 POST/PUT 等方法,把數據加到 body 上,并設好 HTTP 頭中的 Content-Type
為 application/x-www-form-urlencoded
,告訴服務端字符串的編碼是什么。
2. 構建 multipart 請求
這部分有點復雜,暫時還沒看。
五、AFURLResponseSerialization
AFURLResponseSerialization 模塊負責解析網絡返回數據,檢查數據是否合法,把服務器返回的 NSData 數據轉成相應的對象。
AFURLResponseSerialization 模塊包括一個協議、一個基類和多個解析特定格式數據的子類,用戶可以很方便地繼承基類 AFHTTPResponseSerializer 去解析更多的數據格式:
- AFURLResponseSerialization 協議,定義了解析響應數據的接口
- AFHTTPResponseSerializer, HTTP 請求響應數據解析器的基類
- AFJSONResponseSerializer,專門解析 JSON 數據的解析器
- 其他數據格式(XML、image、plist等)的響應解析器
- AFCompoundResponseSerializer,組合解析器,可以將多個解析器組合起來,以同時支持多種格式的數據解析
<div align="center">圖 5 AFURLResponseSerialization 類圖</div>
AFURLResponseSerialization 模塊響應解析機制主要涉及到兩個核心方法:
- AFHTTPResponseSerializer 中定義、實現的
-validateResponse:data:error:
方法 - AFURLResponseSerialization 協議定義的
-responseObjectForResponse:data:error:
方法
1. -validateResponse:data:error:
方法
AFHTTPResponseSerializer 作為解析器基類,提供了 acceptableContentTypes
和 acceptableStatusCodes
兩個屬性,并提供了 acceptableStatusCodes
的默認值,子類可以通過設置這兩個屬性的值來進行自定義配置。AFHTTPResponseSerializer 中的 -validateResponse:data:error:
方法會根據這兩個屬性值來判斷響應的文件類型 MIMEType
和狀態碼 statusCode
是否合法。
比如 AFJSONResponseSerializer 中設置了 acceptableContentTypes
的值為 [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]
,如果服務器返回的 Content-Type
不是這三者之一,-validateResponse:data:error:
方法就會返回解析失敗的錯誤信息。
案例:在網上看到有開發者就曾經遇到過相關的問題
——服務器返回的數據是 JSON 數據,但是Content-Type
卻不符合要求,結果導致解析失敗。
2. -responseObjectForResponse:data:error:
方法
AFJSONResponseSerializer 等子類中實現的 -responseObjectForResponse:data:error:
方法會先調用 -validateResponse:data:error:
方法驗證數據是否合法,拿到驗證結果后,接著這里有個補充判斷條件——如果是 content type 的錯誤就直接返回 nil,因為數據類型不符合要求,就沒必要再繼續解析數據了,如果是 status code 的錯誤就繼續解析,因為數據本身沒問題,而錯誤信息有可能就在返回的數據中,所以這種情況下會將 status code 產生的錯誤信息和解析后的數據一起“打包”返回。
AFJSONResponseSerializer 在解析數據后還提供了移除 NSNull 的功能,主要是為了防止服務端返回 null 時導致解析后的數據中有了脆弱的 NSNull,這樣很容易導致崩潰(但是之前一直沒發現這個功能[捂臉])。
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
// 如果是 content type 的錯誤就直接返回,因為數據類型不符合要求
// 如果是 status code 的錯誤就繼續解析,因為錯誤信息有可能就在返回的數據中
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
NSError *serializationError = nil;
id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
...
// 移除 NSNull(如果需要的話),默認是 NO
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
...
return responseObject;
}
六、AFSecurityPolicy
幾個關鍵字:HTTPS,TSL,SSL,SSL Pinning,非對稱加密算法
1. 預備知識點
1.1 為什么要使用 HTTPS
因為直接使用 HTTP 請求,就會有可能遇到以下幾個安全問題:
- 傳輸數據被竊聽:HTTP 報文使用明文方式發送,而且 HTTP 本身不具備加密的功能,而互聯網是由聯通世界各個地方的網絡設施組成,所有發送和接收經過某些設備的數據都可能被截獲或窺視。
- 認證問題:
- 無法確認你發送到的服務器就是真正的目標服務器(可能服務器是偽裝的)
- 無法確定返回的客戶端是否是按照真實意圖接收的客戶端(可能是偽裝的客戶端)
- 無法確定正在通信的對方是否具備訪問權限,Web 服務器上某些重要的信息,只想發給特定用戶
- 傳輸內容可能被篡改:請求或響應在傳輸途中,可能會被攻擊者攔截并篡改內容,也就是所謂的中間人攻擊(Man-in-the-Middle attack,MITM)。
1.2 HTTPS 的出現
HTTPS,也稱作 HTTP over TLS,HTTPS 就是基于 TLS 的 HTTP 請求。TLS 是一種基于 TCP 的加密協議,它主要做了兩件事:傳輸的兩端可以互相驗證對方的身份,以及驗證后加密所傳輸的數據。
HTTPS 通過驗證和加密兩種手段的結合解決了上面 HTTP 所面臨的 3 個安全問題。
1.3 SSL/TLS 協議
SSL(Secure Sockets Layer):SSL 協議是一種數據加密協議,為了保證網絡數據傳輸的安全性,網景公司設計了 SSL 協議用于對 HTTP 協議傳輸的數據進行加密,從而就誕生了 HTTPS。
TLS(Transport Layer Security):TLS 協議是 SSL 協議的升級版。1999年,互聯網標準化組織 ISOC 接替 NetScape 公司,發布了 SSL 的升級版 TLS 1.0版。
1.4 HTTPS 與 HTTP 的區別是什么?
|
HTTP | HTTPS |
---|---|---|
URL |
http:// 開頭,并且默認使用端口 80 |
https:// 開頭,并且默認使用端口 443 |
數據隱私性 | 明文傳輸,不加密傳輸數據 | 基于 TLS 的加密傳輸 |
身份認證 | 不認證 | 正式傳輸數據前會進行證書認證,第三方無法偽造服務端(客戶端)身份 |
數據完整性 | 沒有完整性校驗過程 | 內容傳輸經過完整性校驗 |
HTTP協議和安全協議(SSL/TLS)同屬于應用層(OSI模型的最高層),具體來講,安全協議(SSL/TLS)工作在 HTTP 之下,傳輸層之上:安全協議向運行 HTTP 的進程提供一個類似于 TCP 的套接字,供進程向其中注入報文,安全協議將報文加密并注入傳輸層套接字;或是從運輸層獲取加密報文,解密后交給對應的進程。嚴格地講,HTTPS 并不是一個單獨的協議,而是對工作在一加密連接(TLS或SSL)上的常規 HTTP 協議的稱呼。
<div align="center">圖 6 協議層</div>
HTTPS 報文中的任何東西都被加密,包括所有報頭和荷載(payload)。除了可能的選擇密文攻擊之外,一個攻擊者所能知道的只有在兩者之間有一連接這件事。
1.5 HTTPS 連接的建立過程
HTTPS在傳輸數據之前需要客戶端與服務端之間進行一次握手,在握手過程中將確立雙方加密傳輸數據的密碼信息。(握手過程采用的非對稱加密,正式傳輸數據時采用的是對稱加密)
HTTPS 的認證有單向認證和雙向認證,這里簡單梳理一下客戶端單向認證時的握手流程:
(1)客戶端發起一個請求,服務端響應后返回一個證書,證書中包含一些基本信息和公鑰。
(2)客戶端里存有各個受信任的證書機構根證書,用這些根證書對服務端返回的證書進行驗證,如果不可信任,則請求終止。
(3)如果證書受信任,或者是用戶接受了不受信的證書,客戶端會生成一串隨機數的密碼 random key,并用證書中提供的公鑰加密,再返回給服務器。
(4)服務器拿到加密后的隨機數,利用私鑰解密,然后再用解密后的隨機數 random key,對需要返回的數據加密,加密完成后將數據返回給客戶端。
(5)最后用戶拿到被加密過的數據,用客戶端一開始生成的那個隨機數 random key,進行數據解密。整個 TLS/SSL 握手過程完成。
<div align="center">圖 7 TLS/SSL 握手過程(單向認證)</div>
完整的 HTTPS 連接的建立過程,包括下面三個步驟:
(1)TCP 協議的三次握手;
(2)TLS/SSL 協議的握手、密鑰協商;
(3)使用共同約定的密鑰開始通信。
<div align="center">圖 8 完整的 HTTPS 連接的建立過程</div>
1.6 HTTPS 傳輸時是如何驗證證書的?怎樣應對中間人偽造證書?
先來看看維基百科上對對稱加密和非對稱加密的解釋:
對稱密鑰加密(英語:Symmetric-key algorithm)又稱為對稱加密、私鑰加密、共享密鑰加密,是密碼學中的一類加密算法。<u>這類算法在加密和解密時使用相同的密鑰,或是使用兩個可以簡單地相互推算的密鑰</u>。實務上,這組密鑰成為在兩個或多個成員間的共同秘密,以便維持專屬的通訊聯系。與公開密鑰加密相比,要求雙方取得相同的密鑰是對稱密鑰加密的主要缺點之一。
公開密鑰加密(英語:public-key cryptography,又譯為公開密鑰加密),也稱為非對稱加密(asymmetric cryptography),一種密碼學算法類型,在這種密碼學方法中,需要一對密鑰(其實這里密鑰說法不好,就是“鑰”),一個是私人密鑰,另一個則是公開密鑰。<u>這兩個密鑰是數學相關,用某用戶密鑰加密后所得的信息,只能用該用戶的解密密鑰才能解密。如果知道了其中一個,并不能計算出另外一個。因此如果公開了一對密鑰中的一個,并不會危害到另外一個的秘密性質</u>。稱公開的密鑰為公鑰;不公開的密鑰為私鑰。
從上面可以看出非對稱加密的特點:非對稱加密有一對公鑰私鑰,用公鑰加密的數據只能通過對應的私鑰解密,用私鑰加密的數據只能通過對應的公鑰解密。這種加密是單向的。
(1)HTTPS 傳輸時是如何驗證證書的呢?
我們以最簡單的為例:一個證書頒發機構(CA),頒發了一個證書 Cer,服務器用這個證書建立 HTTPS 連接,同時客戶端在信任列表里有這個 CA 機構的根證書。
CA 機構頒發的證書 Cer 里包含有證書內容 Content,以及證書加密內容 Crypted Content(數字簽名),這個加密內容 Crypted Content 就是用這個證書機構的私鑰對內容 Content 加密的結果。
+-------------------+
| Content |
+-------------------+
| Crypted Content |
+-------------------+
證書 Cer
建立 HTTPS 連接時,服務端會把證書 Cer 返回給客戶端,客戶端系統里的 CA 機構根證書有這個 CA 機構的公鑰,用這個公鑰對證書 Cer 的加密內容 Crypted Content 解密得到 Content,跟證書 Cer 里的內容 Content 對比,若相等就通過驗證。大概的流程如下:
+-----------------------------------------------------+
| crypt with private key |
| Content ------------------------> Crypted Content |
Server | |
| 證書 Cer |
+-----------------------------------------------------+
||
||
\/
+-----------------------------------------------------+
| |
| Content & Crypted Content |
Client | | | |
| | 證書 Cer | |
+------------------|---------------|------------------+
| |
| | derypt with public key
| |
\/ 相等? \/
Content ------ Decrypted Content
(2)怎樣應對中間人偽造證書?
因為中間人不會有 CA 機構的私鑰,即便偽造了一張證書,但是私鑰不對,加密出來的內容也就不對,客戶端也就無法通過 CA 公鑰解密,所以偽造的證書肯定無法通過驗證。
1.7 Certificate Pinning 是什么?
如果一個客戶端通過 TLS 和服務器建立連接,操作系統會驗證服務器證書的有效性(一般是按照 X.509 標準)。當然,有很多手段可以繞開這個校驗,最直接的是在 iOS 設備上安裝證書并且將其設置為可信的。這種情況下,實施中間人攻擊也不是什么難事。不過通過 Certificate Pinning 可以解決這個問題。
A client that does key pinning adds an extra step beyond the normal X.509 certificate validation.
—— Wikipedia:Certificate Pinning
Certificate Pinning ,可以理解為證書綁定,有時候又叫 SSL Pinning,其實更準確的叫法應該是 Public Key Pinning(公鑰綁定)。證書綁定是一種檢測和防止“中間人攻擊”的方式,客戶端直接保存服務端的證書,當建立 TLS 連接后,應立即檢查服務器的證書,<u>不僅要驗證證書的有效性,還需要確定證書是不是跟客戶端本地的證書相匹配</u>。考慮到應用和服務器需要同時升級證書的要求,這種方式比較適合應用在訪問自家服務器的情況下。
為什么直接對比就能保證證書沒問題?
如果中間人從客戶端取出證書,再偽裝成服務端跟其他客戶端通信,它發送給客戶端的這個證書不就能通過驗證嗎?確實可以通過驗證,但后續的流程走不下去,因為下一步客戶端會用證書里的公鑰加密,中間人沒有這個證書的私鑰就解不出內容,也就截獲不到數據,這個證書的私鑰只有真正的服務端有,中間人偽造證書主要偽造的是公鑰。
什么情況下需要使用 Certificate Pinning?
- 就像前面所說的,常規的驗證方式并不能避免遭遇中間人攻擊,因為如果所訪問網站的證書是自制的,而且在客戶端上通過手動安裝根證書信任了,此時就很容易被惡意攻擊了(還記得你訪問 12306 時收到的證書驗證提醒嗎)。
- 如果服務端的證書是從受信任的的 CA 機構頒發的,驗證是沒問題的,但 CA 機構頒發證書比較昂貴,小企業或個人用戶可能會選擇自己頒發證書,這樣就無法通過系統受信任的 CA 機構列表驗證這個證書的真偽了。
2. AFSecurityPolicy 的實現
2.1 AFSecurityPolicy 的作用
NSURLConnection 和 NSURLSession 已經封裝了 HTTPS 連接的建立、數據的加密解密功能,我們直接使用 NSURLConnection 或者 NSURLSession 也是可以訪問 HTTPS 網站的,但 NSURLConnection 和 NSURLSession 并沒有驗證證書是否合法,無法避免中間人攻擊。要做到真正安全通訊,需要我們手動去驗證服務端返回的證書(系統提供了 SecTrustEvaluate
函數供我們驗證證書使用)。
AFSecurityPolicy 幫我們封裝了證書驗證的邏輯,讓用戶可以輕易使用,除了在系統的信任機構列表里驗證,還支持 SSL Pinning 方式的驗證。
2.2 使用方法
如果是權威機構頒發的證書,不需要任何設置。
如果是自簽名證書,但是不做證書綁定,直接按照下面的代碼實現即可:
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
// 允許無效證書(包括自簽名證書),必須的
policy.allowInvalidCertificates = YES;
// 是否驗證域名的CN字段
// 不是必須的,但是如果寫YES,則必須導入證書。
policy.validatesDomainName = NO;
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:<#MyAPIBaseURLString#>]];
manager.securityPolicy = securityPolicy;
如果是自簽名證書,而且還要做證書綁定,就需要把自簽的服務端證書,或者自簽的CA根證書導入到項目中(把 cer 格式的服務端證書放到 APP 項目資源里,AFSecurityPolicy 會自動尋找根目錄下所有 cer 文件,當然你也可以自己讀取),然后再選擇驗證證書或者公鑰。
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// 允許無效證書(包括自簽名證書),必須的
policy.allowInvalidCertificates = YES;
// 是否驗證域名的CN字段,不是必須的
policy.validatesDomainName = NO;
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:<#MyAPIBaseURLString#>]];
manager.securityPolicy = securityPolicy;
2.3 AFSecurityPolicy 的實現
詳細說明見源碼注釋。
在 AFURLSessionManager 中實現的 -URLSession:didReceiveChallenge:completionHandler:
方法中,根據 NSURLAuthenticationChallenge 對象中的 authenticationMethod,來決定是否需要驗證服務器證書,如果需要驗證,則借助 AFSecurityPolicy 來驗證證書,驗證通過則創建 NSURLCredential,并回調 handler:
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
/*
NSURLSessionAuthChallengeUseCredential:使用指定的證書
NSURLSessionAuthChallengePerformDefaultHandling:默認方式處理
NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消整個請求
NSURLSessionAuthChallengeRejectProtectionSpace:
*/
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
// 此處服務器要求客戶端的接收認證挑戰方法是 NSURLAuthenticationMethodServerTrust,也就是說服務器端需要客戶端驗證服務器返回的證書信息
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 客戶端根據安全策略驗證服務器返回的證書
// AFSecurityPolicy 在這里的作用就是,使得在系統底層自己去驗證之前,AF可以先去驗證服務端的證書。如果通不過,則直接越過系統的驗證,取消https的網絡請求。否則,繼續去走系統根證書的驗證(??)。
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 信任的話,就創建驗證憑證去做系統根證書驗證
// 創建 NSURLCredential 前需要調用 SecTrustEvaluate 方法來驗證證書,這件事情其實 AFSecurityPolicy 已經幫我們做了
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
// 不信任的話,就直接取消整個請求
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
// 疑問:這個 completionHandler 是用來干什么的呢?credential 又是用來干什么的呢?
completionHandler(disposition, credential);
}
}
而 AFSecurityPolicy 的核心就在于 -evaluateServerTrust:forDomain:
方法,該方法中主要做了四件事:
- 設置驗證標準(
SecTrustSetPolicies
),為認證做準備 - 處理 SSLPinningMode 為
AFSSLPinningModeNone
的情況——如果允許無效的證書(包括自簽名證書)就直接返回 YES,不允許的話就在系統的信任機構列表里驗證服務端證書。 - 處理 SSLPinningMode 為
AFSSLPinningModeCertificate
的情況,認證證書——設置證書錨點->驗證服務端證書->匹配服務端證書鏈 - 處理 SSLPinningMode 為
AFSSLPinningModePublicKey
的情況,認證公鑰——匹配服務端證書公鑰
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
/*
AFSecurityPolicy 的四個主要屬性:
SSLPinningMode - 證書認證模式
pinnedCertificates - 用來匹配服務端證書信息的證書,這些證書保存在客戶端
allowInvalidCertificates - 是否支持無效的證書(包括自簽名證書)
validatesDomainName - 是否去驗證證書域名是否匹配
SSLPinningMode 提供的三種證書認證模式:
AFSSLPinningModeNone - 沒有 SSL pinning
AFSSLPinningModePublicKey - 用證書綁定方式驗證,客戶端要有服務端的證書拷貝,只是驗證時只驗證證書里的公鑰,不驗證證書的有效期等信息
AFSSLPinningModeCertificate - 用證書綁定方式驗證證書,需要客戶端保存有服務端的證書拷貝,這里驗證分兩步,第一步驗證證書的域名/有效期等信息,第二步是對比服務端返回的證書跟客戶端返回的是否一致。
*/
// 判斷互相矛盾的情況:
// 如果有域名,而且還要允許自簽證書,同時還要驗證域名的話,就一定要驗證服務器返回的證書是否匹配客戶端本地的證書了
// 所以必須滿足兩個條件:A驗證模式不能為 FSSLPinningModeNone;添加到項目里的證書至少 1 個。
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
// 為 serverTrust 設置 policy,也就是告訴客戶端如何驗證 serverTrust
// 如果要驗證域名的話,就以域名為參數創建一個 SecPolicyRef,否則會創建一個符合 X509 標準的默認 SecPolicyRef 對象
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
// 驗證證書是否有效
if (self.SSLPinningMode == AFSSLPinningModeNone) {
// 如果不做證書綁定,就會跟瀏覽器一樣在系統的信任機構列表里驗證服務端返回的證書(如果是自己買的證書,就不需要綁定證書了,可以直接在系統的信任機構列表里驗證就行了)
// 如果允許無效的證書(包括自簽名證書)就會直接返回 YES,不允許的話就會對服務端證書在系統的信任機構列表里驗證。如果服務器證書無效,并且不允許無效證書,就會返回 NO
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
// 如果不是 AFSSLPinningModeNone,而且證書在系統的信任機構列表里驗證失敗,同時不允許無效的證書(包括自簽名證書)時,直接返回評估失敗
// (如果是自簽名的證書,驗證時就需要做證書綁定,或者直接在系統的信任機構列表里中添加根證書)
return NO;
}
// 根據 SSLPinningMode 對服務端返回的證書進行 SSL Pinning 驗證,也就是說拿本地的證書和服務端證書進行匹配
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
// 把證書 data 轉成 SecCertificateRef 類型的數據,保證返回的證書都是 DER 編碼的 X.509 證書
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 1.
// 將 pinnedCertificates 設置成需要參與驗證的 Anchor Certificate(錨點證書,嵌入到操作系統中的根證書,也就是權威證書頒發機構頒發的自簽名證書),通過 SecTrustSetAnchorCertificates 設置了參與校驗錨點證書之后,假如驗證的數字證書是這個錨點證書的子節點,即驗證的數字證書是由錨點證書對應CA或子CA簽發的,或是該證書本身,則信任該證書,具體就是調用 SecTrustEvaluate 來驗證。
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
// 自簽證書在之前是驗證通過不了的,在這一步,把我們自己設置的證書加進去之后,就能驗證成功了。
// 再去調用之前的 serverTrust 去驗證該證書是否有效,有可能經過這個方法過濾后,serverTrust 里面的 pinnedCertificates 被篩選到只有信任的那一個證書
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// 注意,這個方法和我們之前的錨點證書沒關系了,是去從我們需要被驗證的服務端證書,去拿證書鏈。
// 服務器端的證書鏈,注意此處返回的證書鏈順序是從葉節點到根節點
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
// 如果我們的證書中,有一個和它證書鏈中的證書匹配的,就返回 YES
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
// 獲取服務器證書公鑰
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
// 判斷自己本地的證書的公鑰是否存在與服務器證書公鑰一樣的情況,只要有一組符合就為真
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}
2.4 技術點
(1) __Require_Quiet
宏中 do-while 的特殊使用
#ifndef __Require_Quiet
#define __Require_Quiet(assertion, exceptionLabel) \
do \
{ \
if ( __builtin_expect(!(assertion), 0) ) \
{ \
goto exceptionLabel; \
} \
} while ( 0 )
#endif
__Require_Quiet
宏中使用了一個 do...while(0)
的循環語句,從邏輯上看這個 do-while 語句完全可以不需要,但是實際上是不能去掉的,原因是為了防止在某種情況下使用該宏時出現語法錯誤。比如,在下面這種情況下,如果沒有 do...while(0)
就在編譯時報錯:
if (xxx)
__Require_Quiet();
else
NSLog("This is else");
(2) Core Foundation 和 Security 框架的 API 的使用
延伸閱讀:
- 關于 HTTPS 請求流程
- TLS/SSL
- 關于數字證書
- 加密算法
- 白話解釋 對稱加密算法 VS 非對稱加密算法
- 關于非對稱加密算法的原理:RSA算法原理(一) (二)
- 認證流程
七、AFNetworkReachabilityManager
暫時還沒看
八、UIKit 擴展
暫時還沒看
九、AFNetworking 2.x
十、AFNetworking 的價值
1. 請求調度:NSURLConnection + NSOperation
在 NSURLConnection 時代,AFNetworking 1.x 的最核心的作用在于多線程下的請求調度——將 NSURLConnection 和 NSOperation 結合,AFURLConnectionOperation 作為 NSOperation 的子類,遵循 NSURLConnectionDelegate 的方法,可以從頭到尾監聽請求的狀態,并儲存請求、響應、響應數據等中間狀態。
2. 更高層次的抽象
顯然,在 NSURLSession 出現之后,AFNetworking 的意義似乎不如以前那么重要了。實際上,雖然它們有一些重疊,AFNetworking 還是可以提供更高層次的抽象。
AFNetworking 幫我們完成了很多繁瑣的工作,這使得我們在業務層的網絡請求變得非常輕松:
- 請求參數和返回數據的序列化,支持多種不同格式的數據解析
- multipart 請求拼接數據
- 驗證 HTTPS 請求的證書
- 請求成功和失敗的回調處理,下載、上傳進度的回調處理
3. block
AFNetworking 將 NSURLSession 散亂的代理回調方法都轉成了 block 形式的 API,除此之外,還提供了一些用于自定義配置的 block,比如發起 multipart 請求時,提供 constructingBody 的 block 接口來拼接數據。
4. 模塊化
AFNetworking 在架構上采用了模塊化的設計,各模塊的職責是明確的、功能是獨立的,我們可以根據自己的需要,選擇合適的模塊組合使用:
- 創建請求
- 序列化 query string 參數
- 確定響應解析行為
- 管理 Session
- HTTPS 認證
- 監視網絡狀態
- UIKit 擴展
十一、問題:
1.AFNetworking 的作用是什么?不用 AFNetworking 直接用系統的 NSURLSession 不可以嗎?AFNetworking 為什么要對 NSURLConnection/NSURLSession 進行封裝?它是如何封裝的?
2.AFNetworking 框架的設計思路和原理是什么?
3.AFNetworking 和 MKNetworkKit 以及 ASIHttpRequest 有什么不同?
4.AFNetworking 2.x 和 AFNetworking 3.x 的區別是什么?
十二、收獲
- 開源項目、專業素養、規范
- 完善的注釋、文檔
- 忽略一些特定的clang的編譯警告
- nullable
- 規范,通過斷言檢測參數的合法性
- 邏輯嚴謹、完善,擴展性好,比如針對用戶可能需要的各種自定義處理提供了 block 回調,基于協議的 serialization 設計
- 萬物皆對象,比如請求 url 參數的解析時,使用了 AFQueryStringPair 對象來表征一個 Query 參數;還有 NSProgress 的使用
- 面向協議編程,提高程序的可擴展性
- 多線程編程時,腦海中要有清晰的線程調度圖
- Unit Test,看到 GitHub 上有個 pr 的討論中多次提到了 Unit Test,原來 Unit Test 對于保證修改后的代碼功能有很大用處,另外就是,有些使用的示例也可以從 test case 中找到
延伸閱讀
- AFNetworking到底做了什么?(一)(系列文章,寫的非常詳細,非常推薦)
- bang:AFNetworking2.0 源碼解析(一)(二)(三)(四)
- Draveness :AFNetworking 源碼解析(一)
- NSHipster: AFNetworking 2.0
- 四種常見的 POST 提交數據方式
- IP,TCP 和 HTTP