AFNetworking
基本是蘋果開發中網絡請求庫的標配,它是一個輕量級的網絡庫,專門針對iOS
和OS X
的網絡應用設計,具有模塊化的架構和豐富的APIs
接口,功能強大並且使用簡單,深受蘋果應用開發人員的喜愛。
本文主要介紹一下AFNetworking
(版本:3.1.0
)的模塊結構、請求的執行過程、網絡狀態監測以及網絡安全的處理等等,從而對AFNetworking
的具體功能、執行過程有一個大致的瞭解,在實際的項目開發過程中,能夠更好的進行應用。
一、結構
下面是AFNetworking
的源碼結構圖,主要分爲:NSURLSession
核心代碼、Reachability
、Security
、Serialization
和UIKit
等5
部分。
AFNetworking
的源碼結構圖:
AFHTTPSessionManager
的依賴關係:
AFNetworking
的網絡數據的序列化(Serialization
)的類結構圖:
NSURLSessionTask
的類結構圖:
各部分功能介紹如下:
1.NSURLSession
這是網絡請求的核心代碼,承擔發送請求、接收數據、異常處理等功能,以及請求和響應的數據序列化功能
2.Reachability
用來監控設備的網絡狀態,只能識別WWAN
和WiFi
這兩種網絡
3.Security
網絡安全的設計,尤其是利用https增強數據安全性
4.Serialization
請求和響應數據的序列化處理
5.UIKit
主要是對一些常用UI控件做網絡交互方便的擴展
二、應用
1.網絡狀態監測
網絡狀態監測,通過AFNetworkReachabilityManager
的單例對象設置網絡狀態變化的block
,然後通過AFStringFromNetworkReachabilityStatus
把當前網絡狀態轉換爲對應的字符串。一般情況下,設置網絡監測的代碼放到AppDelegate.m
裏,監測整個項目的網絡狀態變化。
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status));
}];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
2.網絡安全
HTTPS
相比HTTP
提高了通信的安全性,而蘋果也建議網絡請求使用HTTPS
,所以有必要了解一下HTTPS
的工作過程(HTTPS通信流程介紹)。而AFNetworking
中的
AFSecurityPolicy
類用來驗證證書是否有效,從而防止被中間人攻擊。在實際開發中,會在AFURLSessionManager
類的初始化方法裏使用默認的安全設置:
- 不允許無效或過期的證書
- 驗證domain名稱
- 不對證書和公鑰進行驗證
// AFURLSessionManager.m
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
...
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
...
}
// AFSecurityPolicy.m
+ (instancetype)defaultPolicy {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
return securityPolicy;
}
部分重要屬性介紹如下:
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
a、AFSSLPinningModeNone:不驗證服務器的整數,完全信任
b、AFSSLPinningModePublicKey:驗證服務器返回的證書中的PublicKey
c、AFSSLPinningModeCertificate:把服務器的返回的證書和本地證書進行驗證
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
這個屬性保存着所有的可用做校驗的證書的集合
@property (nonatomic, assign) BOOL allowInvalidCertificates;
是否允許無效或過期的證書,默認是不允許
@property (nonatomic, assign) BOOL validatesDomainName;
是否驗證證書中的域名domain,默認是驗證
3.HTTP請求
HTTP
請求在開發中常用的就是GET
、POST
這兩種類型,針對這兩種請求方式的區別請參考HTTP協議格式詳解。本文從源碼實現的角度,不會詳細介紹各個類的功能及依賴關係,只是面向應用的角度來分析一下這兩種請求如何實現的:
整個網絡協議的流程圖:
GET/POST
AFNetworking
發起的網絡請求,GET
和POST
的差異就是在設置請求的URL
和HTTPBody
時的差異。下面根據上面的流程圖詳細介紹一下具體的代碼實現:
步驟一:發起網絡請求
- 通過
requestSerializer.timeoutInterval
設置請求超時時間 - 通過
responseSerializer.acceptableContentTypes
設置客戶端可接收的數據類型
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = 10;
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", @"text/plain", nil];
[manager GET:@"https://app.chaoaicai.com/api/todayApi/discoveryInfo.app" parameters:@{@"name": @"kelvin"} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@", error);
}];
步驟二、三:創建NSMutableURLRequest 和 NSURLSessionDataTask
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
...
// 創建Request
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
...
// 創建dataTask
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
...
}];
...
}
步驟四:設置AFURLSessionManager的代理
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
// 創建dataTask
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
// 設置dataTask的代理
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
步驟五:調用NSURLSessionDataTask的resume方法開始網絡請求
通過調用NSURLSessionTask
的resume
來啓動任務,也可以調用suspend
來掛起任務
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
...
[dataTask resume];
...
}
步驟六:AFURLSessionManagerTaskDelegat的方法處理回調數據
這個方法裏主要功能是處理接收到的數據,調用保存的任務完成的block
,併發送接收完成的通知
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
...
if(error){
...
} else {
...
}
}
步驟7:清理NSURLSessionDataTask配置
刪除NSURLSessionDataTask
的配置信息,如:刪除監控收發數據進度的通知、任務開啓或掛起的通知、從mutableTaskDelegatesKeyedByTaskIdentifier
中刪除任務的delegate
等等。之後,回調用戶設置的block
(成功或者失敗的回調),等block
執行結束後,就結束這個GET
請求。
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
// 調用代理方法處理接收到的數據
[delegate URLSession:session task:task didCompleteWithError:error];
// 清理dataTask的配置信息
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
// 回調用戶的block
self.taskDidComplete(session, task, error);
}
}
// 清理dataTask配置
- (void)removeDelegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
[self.lock lock];
[delegate cleanUpProgressForTask:task];
[self removeNotificationObserverForTask:task];
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
[self.lock unlock];
}
下面介紹一下GET
和POST
在請求時,封裝URL
和HTTPBody
的差別:
- GET請求
把parameters
字典轉換爲key=value&key=value
的形式,並附加在用戶設置的url
的後面作爲請求的url
- POST請求
把parameters
字典轉換爲key=value&key=value
的形式,然後添加到HTTPBody
裏面作爲請求體發送
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
// 添加用戶配置的header的key-value值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
...
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
// GET 請求
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
// POST 請求
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
4.普通請求(直接用NSURLSession)
AFNetworking
除了直接用GET
或POST
發送請求外,還可以直接用AFURLSessionManager
發送網絡請求,而GET
或POST
內部其實也是調用的AFURLSessionManager
,這樣我們也可以手動設置任務的開啓或掛起。
下面是AFNetworking
的Github
的示例代碼,關於上傳或下載代碼,請參考AFNetworking的介紹
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://fishbay.cn"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[dataTask resume];
至此,AFNetworking的分析到此結束,本文只是從應用的角度分析了一下源碼的實現,如有不足之處,歡迎指正。(本文部分圖片來自互聯網,版權歸原作者所有)
參考資料