AFNetworking源碼分析

說到AFNetwokring這個強大第三方網絡請求庫,大家應該都不陌生吧,ios開發、mac開發都經常用,主要是他使用起來簡單、方便。下面我們看看他的源碼,來探討一下吧。

首先,我們一起來看一下它的框架的組成部分吧。


上面的圖片我從AFNetworking的文件中接的圖,我們可以看出,包含5個部分,其實AFHTTPSessionManager是AFURLSessionManager的子類,所以說它的組成是四個部分:

網絡通信模塊(AFHTTPSessionmanager、AFURLSessionManager)

網絡狀態監聽模塊(AFNetworkReachabilityManager)

網絡通信信息序列化反序列化策略模塊(AFURLRequestSErialization、AFURLResponseSerialization)

網絡通信安全策略模塊(AFSecurityPolicy)

對ios UIKit庫的拓展

其核心當然就是網絡通信模塊AFURLSessionManager,這個類是對NSURLSession的進一步的封裝

其他模塊均是配合網絡通信或對已有UIKIt的擴展

一、初始化方法

最終都會到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法中來了。

這個方法主要做了什么事情呢?

1、調父類的- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 這個方法里面設置了

隊列為非主線程隊列,隊列的并發數為1,

初始化了session,設置了代理,

初始化了網絡通信監聽、網絡安全策略

,初始化了保存網絡請求任務、對應的任務代理到字典中?

遍歷session中的任務,將任務對應的任務代理設為nil,請求代理

2、設置默認AFURLRequestSErialization、AFURLResponseSerialization,分別是請求序列對象和響應序列對象,這個兩個東西會在NSURLRequest中設置請求頭、請求體等一些信息中用到

上面的分析,可以看到,主要的東西還在父類中做的,里面初始化方法里便利session,將session中任務的代理清空是一種防御性編程。

然后來看GET請求

- (NSURLSessionDataTask *)GET:(NSString *)URLString參數:(ID)參數進度:(void(^)(NSProgress * _Nonnull))downloadProgress成功:(無效(^)(NSURLSessionDataTask * _Nonnull,id _Nullable))成功失敗:(void(^)(NSURLSessionDataTask * _Nullable,NSError * _Nonnull))失敗

{//生成一個任務

NSURLSessionDataTask * dataTask = [self dataTaskWithHTTPMethod:@“GET”URLString:URLString參數:參數上傳進度:無downloadProgress:downloadProgress成功:成功失敗:失敗];

//開始網絡請求[dataTask resume];返回dataTask;}

這里主要生成一個NSURLSessionDataTask來進行網絡請求

我們繼續往父類里看,看看這個方法到底做了什么:

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)方法URLString:(NSString *)URLString參數:(ID)參數uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressdownloadProgress :(可空(void)(^)(NSProgress * downloadProgress))downloadProgress成功:(void(^)(NSURLSessionDataTask *,id))成功失敗:(void(^)(NSURLSessionDataTask *,NSError *))失敗{NSError * serializationError = nil;

//把參數,還有各種東西轉化為一個請求

NSMutableURLRequest * request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

If(serializationError){如果(失敗)

{#pragma clang診斷推送#pragma clang診斷忽略“-WgnU”//如果解析錯誤,直接返回dispatch_async(self.completionQueue?:dispatch_get_main_queue(),^ {失敗(nil,serializationError);});

#pragma clang診斷流行}返回零;}__block NSURLSessionDataTask * dataTask = nil;dataTask = [self dataTaskWithRequest:request上傳進度:上傳進度downloadProgress:downloadProgresscompletionHandler:^(NSURLResponse * __unused response,id responseObject,NSError * error){如果(錯誤){如果(失敗){失敗(dataTask,錯誤);}} else {如果(成功){成功(dataTask,responseObject);}}}];返回dataTask;}

這個

這個方法做了兩件事:

1、用self.requestSerializer和各種參數去獲取一個最終網絡請求需要的NSMutableURLRequest

2、調用另外一個方法dataTaskWithRequest去拿到我們需要NSURLSessionDataTask,并且在回掉中調用成功或失敗的回掉

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)方法URLString:(NSString *)URLString參數:(ID)參數uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressdownloadProgress :(可空(void)(^)(NSProgress * downloadProgress))downloadProgress成功:(void(^)(NSURLSessionDataTask *,id))成功失敗:(void(^)(NSURLSessionDataTask *,NSError *))失敗{NSError * serializationError = nil;//把參數,還有各種東西轉化為一個請求NSMutableURLRequest * request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];if(serializationError){如果(失敗){#pragma clang診斷推送#pragma clang診斷忽略“-WgnU”//如果解析錯誤,直接返回dispatch_async(self.completionQueue?:dispatch_get_main_queue(),^ {失敗(nil,serializationError);});#pragma clang診斷流行}返回零;}__block NSURLSessionDataTask * dataTask = nil;dataTask = [self dataTaskWithRequest:request上傳進度:上傳進度downloadProgress:downloadProgresscompletionHandler:^(NSURLResponse * __unused response,id responseObject,NSError * error){如果(錯誤){如果(失敗){失敗(dataTask,錯誤);}} else {如果(成功){成功(dataTask,responseObject);}}}];返回dataTask;}

我們繼續往下看:當解析錯誤,我們直接調用failuer失敗模塊回去,里面有個self.completionQueue,這是我們自定義的,這個是gcd隊列,如果設置就從這個隊列中回掉了,不從主隊列中回掉了

實際上這個隊列還是很有用,有些公司有自己的一套數據加密解密解析模式,所以我們回掉過來的數據并不想在主隊列,我們可以在這個隊列對數據進行解析,然后在回到主線程中。

(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)請求uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressBlockdownloadProgress:(可空(void)(^)(NSProgress * downloadProgress))downloadProgressBlockcompletionHandler:(可空(void)(^)(NSURLResponse *響應,id _Nullable響應對象,NSError * _Nullable錯誤))completionHandler {__block NSURLSessionDataTask * dataTask = nil;//第一件事,創建NSURLSessionDataTask,里面適配了iOS8上以下taskIdentifiers,函數創建任務對象。//其實現應該是因為iOS 8.0以下版本中會并發地創建多個任務對象,而同步有沒有好,導致taskIdentifiers不唯一...這邊做了一個串行處理url_session_manager_create_task_safely(^ {dataTask = [self.session dataTaskWithRequest:request];});[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];返回dataTask;}

我們注意到這個方法非常簡單,就調用了一個url_session_manager_create_task_safely()函數,傳了一個塊進去座里就是iOS的原生生成dataTask的方法。此外,還調用了一個addDelegateForDataTask的方法。

我們到這先到這個函數里去看看:

static void url_session_manager_create_task_safely(dispatch_block_t block){if(NSFoundationVersionNumber

方法非常簡單,關鍵是理解這么做的目的:為什么我們不直接去調用

dataTask = [self.session dataTaskWithRequest:request];

非要繞這么一圈,我們點進去錯誤日志里看看,原來這是為了適配iOS8上的以下,創建會話的時候,偶發的情況會出現會話的屬性taskIdentifier這個值不唯一,而這個taskIdentifier是我們后面來映射代表的關鍵,所以它必須是唯一的。

具體原因應該是NSURLSession內部去生成任務的時候是用多線程并發去執行的。想通了這一點,我們就很好解決了,我們只需要在iOS8上以下同步串行的去生成任務就可以防止這一問題發生(如果還是不理解同步串行的原因,可以看看注釋)。

題外話:很多同學都會抱怨為什么同步我從來用不到,看,有用到的地方了吧,很多東西不是沒用,而只是你想不到怎么用。

我們接著看到:

[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

調用到:

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTaskuploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressBlockdownloadProgress:(可空(void)(^)(NSProgress * downloadProgress))downloadProgressBlockcompletionHandler:(void(^)(NSURLResponse * response,id responseObject,NSError * error))completionHandler{AFURLSessionManagerTaskDelegate * delegate = [[AFURLSessionManagerTaskDelegate alloc] init];// AFURLSessionManagerTaskDelegate與AFURLSessionManager建立相互的關系delegate.manager = self;delegate.completionHandler = completionHandler;//這個taskDescriptionForSessionTasks用來發送開始和掛起通知的時候會用到,就是用這個值來張貼通知,來兩者對應dataTask.taskDescription = self.taskDescriptionForSessionTasks;// *****將AF委托對象與dataTask建立關系[self setDelegate:delegate forTask:dataTask];//設置AF委托的上傳進度,下載進度塊。delegate.uploadProgressBlock = uploadProgressBlock;delegate.downloadProgressBlock = downloadProgressBlock;}

總結一下:

1)這個方法,生成了一個AFURLSessionManagerTaskDelegate,這個其實就是AF的自定義代理。我們請求傳來的參數,都賦值給這個AF的代理了。

2)delegate.manager = self;代理把AFURLSessionManager這個類作為屬性了,我們可以看到:

@屬性(非原子,弱)AFURLSessionManager *管理器;

這個屬性是弱引用的,所以不會存在循環引用的問題。

3)我們調用了[self setDelegate:delegate forTask:dataTask];

我們進去看看這個方法做了什么:

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)委托forTask:(NSURLSessionTask *)任務{//斷言,如果沒有這個參數,調試下墜毀在這NSParameterAssert(任務);NSParameterAssert(代表);//加鎖保證字典線程安全[self.lock lock];//將AF委托放入以taskIdentifier標記的詞典中(同一個NSURLSession中的taskIdentifier是唯一的)self.mutableTaskDelegatesKeyedByTaskIdentifier [@(task.taskIdentifier)] = delegate;//為AF代表設置任務的進度監聽[委托setupProgressForTask:任務];//添加任務開始和暫停的通知[self addNotificationObserverForTask:task];[self.lock解鎖];}

這個方法主要就是把AF代理和任務建立映射,存在了一個我們事先聲明好的字典里。

而要加鎖的原因是因為本身我們這個字典屬性是可變的,是線程不安全的。而我們對這些方法的調用,確實是會在復雜的多線程環境中,后面會仔細提到線程問題。

還有個[delegate setupProgressForTask:task];我們到方法里去看看:

- (void)setupProgressForTask:(NSURLSessionTask *)task {__weak __typeof __(task)weakTask = task;//拿到上傳下載期望的數據大小self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;//將上傳與下載進度和任務綁定在一起,直接取消掛起恢復進度條,可以取消...任務[self.uploadProgress setCaslable:YES];[self.uploadProgress setCancellationHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strong任務取消];}];[self.uploadProgress setPausable:YES];[self.uploadProgress setPausingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask暫停];}];如果([self.uploadProgress respondsToSelector:@selector(setResumingHandler :)]){[self.uploadProgress setResumingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask簡歷];}];}[self.downloadProgress setCancellable:YES];[self.downloadProgress setCancellationHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strong任務取消];}];[self.downloadProgress setPausable:YES];[self.downloadProgress setPausingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask暫停];}];如果([self.downloadProgress respondsToSelector:@selector(setResumingHandler :)]){[self.downloadProgress setResumingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask簡歷];}];}//觀察任務的這些屬性[任務addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))選項:NSKeyValueObservingOptionNew上下文:NULL];[任務addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))選項:NSKeyValueObservingOptionNew上下文:NULL];[任務addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesSent))選項:NSKeyValueObservingOptionNew上下文:NULL];[任務addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))選項:NSKeyValueObservingOptionNew上下文:NULL];//觀察進度這兩個屬性[self.downloadProgress addObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted))選項:NSKeyValueObservingOptionNew上下文:NULL];[self.uploadProgress addObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted))選項:NSKeyValueObservingOptionNew上下文:NULL];}

這個方法也非常簡單,主要做了以下幾件事:

1)設置downloadProgress與uploadProgress的一些屬性,并且把兩個和任務的任務狀態綁定在了一起。注意這兩個都是NSProgress的實例對象,(這里可能又一群小伙楞在這了,這是個什么...)簡單來說,這就是iOS7引進的一個用來管理進度的類,可以開始,暫停,取消,完整的對應了任務的各種狀態,當進度進行各種操作的時候,任務也會引發對應操作。

2)給的任務和進度的各個屬及添加志愿監聽,至于監聽了干什么用,我們接著往下看:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {//是任務如果([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]){//給進度條賦新值如果([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]){self.downloadProgress.completedUnitCount = [更改[NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]){self.downloadProgress.totalUnitCount = [change [NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]){self.uploadProgress.completedUnitCount = [更改[NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]){self.uploadProgress.totalUnitCount = [change [NSKeyValueChangeNewKey] longLongValue];}}//上面的賦新值會觸發這兩個,調用塊回調,用戶拿到進度else if([object isEqual:self.downloadProgress]){if(self.downloadProgressBlock){self.downloadProgressBlock(對象);}}else if([object isEqual:self.uploadProgress]){if(self.uploadProgressBlock){self.uploadProgressBlock(對象);}}}

方法非常簡單直觀,主要就是如果任務觸發志愿,則給進度進度賦值,應為賦值了,所以會觸發進步的志愿,也會調用到這里,然后去執行我們傳進來的downloadProgressBlock和uploadProgressBlock。主要的作用就是為了讓進度實時的傳遞。

主要是觀摩一下大神的寫代碼的結構,這個解耦的編程思想,不愧是大神...

還有一點需要注意:我們之前的setProgress和這個志愿監聽,都是在我們AF自定義的委托內的,是有一個任務就會有一個代表的所以說我們是每個任務都會去監聽這些屬性,分別在各自的AF代理內。看到這,可能有些小伙伴會有點亂,沒關系。等整個講完之后我們還會詳細的去講捋一捋經理,任務,還有AF自定義代理三者之前的對應關系。

到這里我們整個對任務的處理就完成了。

2,HTTPS認證- (void)URLSession:(NSURLSession *)會話didReceiveChallenge:(NSURLAuthenticationChallenge *)挑戰completionHandler:(void(^)(NSURLSessionAuthChallengeDisposition disposition,NSURLCredential * credential))completionHandler{//挑戰處理類型為默認/ *NSURLSessionAuthChallengePerformDefaultHandling:默認方式處理NSURLSessionAuthChallengeUseCredential:使用指定的證書NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰* /NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;__block NSURLCredential * credential = nil;// sessionDidReceiveAuthenticationChallenge是自定義方法,用來如何應對服務器端的認證挑戰如果(self.sessionDidReceiveAuthenticationChallenge){disposition = self.sessionDidReceiveAuthenticationChallenge(session,challenge,&credential);} else {//此處服務器要求客戶端的接收認證挑戰方法是NSURLAuthenticationMethodServerTrust//也就是說服務端需要客戶端返回一個根據認證挑戰的保護空間提供的信任(即challenge.protectionSpace.serverTrust)產生的挑戰證書。//而這個證書就需要使用credentialForTrust:來創建一個NSURLCredential對象如果([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){//基于客戶端的安全策略來決定是否信任該服務器,不信任的話,也就沒必要響應挑戰如果([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]){//創建挑戰證書(注:挑戰方式為UseCredential和PerformDefaultHandling都需要新建挑戰證書)credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];//確定挑戰的方式如果(憑證){//證書挑戰處置= NSURLSessionAuthChallengeUseCredential;} else {//默認挑戰唯一區別,下面少了這一步!處置= NSURLSessionAuthChallengePerformDefaultHandling;}} else {//取消挑戰處置= NSURLSessionAuthChallengeCancelAuthenticationChallenge;}} else {//默認挑戰方式處置= NSURLSessionAuthChallengePerformDefaultHandling;}}//完成挑戰if(completionHandler){completionHandler(處置,憑證);}

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容