iOS網路框架AFNetworking3.1.0底層原始碼解讀
AFNetworking基本是iOS開發中的網路第三方庫標配,本文基於AFNetworking3.1.0版本。咱們主要從使用的角度來介紹AFNetworking的發起Get請求的過程,偏重於解讀過程,解讀當你使用AFNetworking發起一個Get請求的時候,AFNetworking內部的處理過程。而不是對AFNetworking原始碼的各個類的程式碼進行深入解析,在原始碼深度解析方面,網路上已經有很多不錯的文章,在文章的末尾我會給出參考連結。
發起請求流程圖get為例
AFNetworking發起一個Get請求的流程圖,大概可以分為這幾個步驟,我會逐個解讀這個流程。
Get請求流程圖
1.AFHTTPSessionManager發起Get請求
發起Get請求
這個方法是AFNetworking的Get請求的起點,其他Get請求的方法也都是直接或者間接呼叫這個方法來發起Get請求。這個方法的程式碼量很少也很直觀,就是呼叫其他方法生成NSURLSessionDataTask物件的例項,然後呼叫NSURLSessionDataTask的resume方法發起請求。
2.建立NSURLSessionDataTask
建立NSURLSessionDataTask
這個方法是建立NSURLSessionDataTask物件例項並返回這個例項。首先建立一個NSMutableURLRequest物件的例項,然後配置。之後是使用NSMutableURLRequest物件的例項建立NSURLSessionDataTask物件例項,然後配置,可以選擇性地傳入各類Block回撥,用於監聽網路請求的進度比如上傳進度,下載進度,請求成功,請求失敗。
3.配置NSMutableURLRequest物件
配置NSMutableURLRequest物件
在這個方法中先使用了url建立了一個NSMutableURLRequest物件的例項,並且設定了HTTPMethod為Get方法(如果是Post方法,那麼這裡就是設定Post方法,以此類推)然後使用KVC的方法設定了NSMutableURLRequest的一些屬性。
//設定NSMutableURLRequest的屬性
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil ;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//allowsCellularAccess 允許使用資料流量
//cachePolicy 快取策略
//HTTPShouldHandleCookies 處理Cookie
//HTTPShouldUsePipelining 批量請求
//networkServiceType 網路狀態
//timeoutInterval 超時
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
配置NSMutableURLRequest物件
先設定HTTP header,之後格式化請求引數,設定引數的編碼型別。這個是這個方法的基本操作流程。對於Get操作來說,引數是直接拼接在請求地址後面。
4.配置NSURLSessionDataTask物件
配置NSURLSessionDataTask物件
之後配置NSMutableURLRequest物件就需要配置NSURLSessionDataTask物件了。主要分為2個步驟,第一個步驟是建立NSURLSessionDataTask物件例項,第二個步驟是給NSURLSessionDataTask物件例項設定Delegate。用於實時瞭解網路請求的過程。
給NSURLSessionDataTask物件例項設定Delegate
AFN的代理統一使用AFURLSessionManagerTaskDelegate物件來管理,使用AFURLSessionManagerTaskDelegate物件來接管NSURLSessionTask網路請求過程中的回撥,然後再傳入AFN內部進行管理。
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
如程式碼所示AFURLSessionManagerTaskDelegate接管了NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate的各種回撥,然後做內部處理。這也是第三方網路請求框架的重點,讓網路請求更加易用,好用。
//通過task的識別符號管理代理
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
通過NSURLSessionTask的taskIdentifier識別符號對delegate進行管理,只要是用於識別該NSURLSessionTask的代理
NSURLSessionTask設定進度回撥
設定各類回撥Block,給NSURLSessionTask使用KVO進行各種過程進度監聽。
//給task新增暫停和恢復的通知
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}
監聽NSURLSessionTask被掛起和恢復的通知
- (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
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
當NSURLSessionTask建立和配置完畢之後,它並不會主動執行,而是需要我們主動呼叫resume方法,NSURLSessionTask才會開始執行。
網路請求回撥
NSURLSessionDelegate方法
NSURLSessionTaskDelegate方法
AFN裡面有關NSURLSessionDelegate的回撥方法非常的多,這裡我們只調和NSURLSessionTask相關的部分方法和KVO處理來進行說明,其他的大家可以參考原始碼細看。
KVO監聽請求過程
收到響應資料
請求完成
對於我們的Get請求來說,我們最關注的莫過於關注請求過程進度,收到響應資料和請求完成這2個回撥。
//KVO監聽的屬性值發生變化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
NSLog(@"countOfBytesReceived");
//這個是在Get請求下,網路響應過程中已經收到的資料量
self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//已經收到
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
NSLog(@"countOfBytesExpectedToReceive");
//這個是在Get請求下,網路響應過程中期待收到的資料量
self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//期待收到
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
NSLog(@"countOfBytesSent");
self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//已經發送
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
NSLog(@"countOfBytesExpectedToSend");
self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//期待發送
}
}
else if ([object isEqual:self.downloadProgress]) {
//下載進度變化
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
//上傳進度變化
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
//收到請求響應
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
NSLog(@"收到請求響應");
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;//允許繼續載入
//是否有收到請求響應的回撥Block
if (self.dataTaskDidReceiveResponse) {
//若有呼叫該Block
disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
}
//是否有請求響應完成的回撥Block
if (completionHandler) {
//若有呼叫該Block
completionHandler(disposition);
}
}
//請求完成
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
NSLog(@"請求完成");
//取出該NSURLSessionTask的代理物件
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];
//NSURLSessionTask任務完成之後,移除該NSURLSessionTask的代理物件
[self removeDelegateForTask:task];
}
//是否有請求完成的回撥Block
if (self.taskDidComplete) {
//若有呼叫改Block
self.taskDidComplete(session, task, error);
}
}
因為在配置NSURLSessionDataTask物件的時候我們有給NSURLSessionTask做了一系列配置,那麼當NSURLSessionDataTask任務完成之後,我們需要將該NSURLSessionDataTask的一系列配置全部清理掉。
這個是配置過程
//通過task的識別符號管理代理
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
那麼對應的清理過程是這樣的,就是設定過程中做了什麼,在清理過程中就需要去掉什麼。
//給task移除delegate
- (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];
}
cleanUpProgressForTask
removeNotificationObserverForTask
關於Post請求
請求序列化方法
#pragma mark - AFURLRequestSerialization
//設定Header和請求引數
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
//判斷header的field是否存在,如果不存在則設定,存在則跳過
if (![request valueForHTTPHeaderField:field]) {
//設定 header
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;
if (parameters) {
//用傳進來的自定義block格式化請求引數
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
//預設的格式化方式
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
//判斷是否是GET/HEAD/DELETE方法, 對於GET/HEAD/DELETE方法,把引數加到URL後面
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
//判斷是否有引數
if (query && query.length > 0) {
//拼接請求引數
NSLog(@"query-->%@",query);
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
if (!query) {
query = @"";
}
//引數帶在body上,大多是POST PUT
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
//設定Content-Type HTTP頭,告訴服務端body的引數編碼型別
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
如果是Post請求,那麼請求引數是沒有拼接在URL上面,而是放在body上,這個是Post和Get請求的最大區別了,其他過程和Get請求並沒有太多區別。
關於HTTPS請求
HTTPS認證1
HTTPS認證2
//Http認證處理
//認證處理
/*
*http://www.cnblogs.com/polobymulberry/p/5140806.html
*web伺服器接收到客戶端請求時,有時候需要先驗證客戶端是否為正常使用者,再決定是夠返回真實資料。
*這種情況稱之為服務端要求客戶端接收挑戰(NSURLAuthenticationChallenge *challenge)。
*接收到挑戰後,
*客戶端要根據服務端傳來的challenge來生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential
*(disposition指定應對這個挑戰的方法,而credential是客戶端生成的挑戰證書,注意只有challenge中認證方法為NSURLAuthenticationMethodServerTrust的時候,才需要生成挑戰證書)。
*最後呼叫completionHandler迴應伺服器端的挑戰。
*/
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
//NSURLAuthenticationChallenge 挑戰處理型別為 預設
/*
*NSURLSessionAuthChallengePerformDefaultHandling:預設方式處理
*NSURLSessionAuthChallengeUseCredential:使用指定的證書
*NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰
*/
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
//自定義方法,用來如何應對伺服器端的認證挑戰
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
//服務端要求客戶端提供證書
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//客戶端評估服務端的安全性
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
//客戶端產生證書
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
//使用指定的證書
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
//預設處理
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
//不處理服務端的認證要求
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
//如果沒有實現方法
/*
*- (void)URLSession:(NSURLSession *)session
*didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
*completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
*/
//那麼URLSession會呼叫下面的方法進入認證處理
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.taskDidReceiveAuthenticationChallenge) {
disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
如果是HTTPS請求的話,那麼會先走上面的2個代理方法進行HTTPS認證,之後繼續其他操作。
總結
AFN發起Get請求主要分為以下步驟:
1.建立NSURLSessionDataTask
2.配置NSURLSessionDataTask
3.設定NSURLSessionDataTask的Delegate
4.呼叫NSURLSessionDataTask的resume方法開始請求
5.在Delegate的方法裡面處理網路請求的各個過程
6.清理NSURLSessionDataTask的配置
其實也就是使用NSURLSessionDataTask的步驟,AFN在這幾個步驟加了一些封裝,讓整個請求過程更加好用,易用。