說到AFNetwokring這個強大第三方網(wǎng)絡請求庫,大家應該都不陌生吧,ios開發(fā)、mac開發(fā)都經(jīng)常用,主要是他使用起來簡單、方便。下面我們看看他的源碼,來探討一下吧。
首先,我們一起來看一下它的框架的組成部分吧。
上面的圖片我從AFNetworking的文件中接的圖,我們可以看出,包含5個部分,其實AFHTTPSessionManager是AFURLSessionManager的子類,所以說它的組成是四個部分:
網(wǎng)絡通信模塊(AFHTTPSessionmanager、AFURLSessionManager)
網(wǎng)絡狀態(tài)監(jiān)聽模塊(AFNetworkReachabilityManager)
網(wǎng)絡通信信息序列化反序列化策略模塊(AFURLRequestSErialization、AFURLResponseSerialization)
網(wǎng)絡通信安全策略模塊(AFSecurityPolicy)
對ios UIKit庫的拓展
其核心當然就是網(wǎng)絡通信模塊AFURLSessionManager,這個類是對NSURLSession的進一步的封裝
其他模塊均是配合網(wǎng)絡通信或?qū)σ延蠻IKIt的擴展
一、初始化方法
最終都會到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法中來了。
這個方法主要做了什么事情呢?
1、調(diào)父類的- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 這個方法里面設置了
隊列為非主線程隊列,隊列的并發(fā)數(shù)為1,
初始化了session,設置了代理,
初始化了網(wǎng)絡通信監(jiān)聽、網(wǎng)絡安全策略
,初始化了保存網(wǎng)絡請求任務、對應的任務代理到字典中?
遍歷session中的任務,將任務對應的任務代理設為nil,請求代理
2、設置默認AFURLRequestSErialization、AFURLResponseSerialization,分別是請求序列對象和響應序列對象,這個兩個東西會在NSURLRequest中設置請求頭、請求體等一些信息中用到
上面的分析,可以看到,主要的東西還在父類中做的,里面初始化方法里便利session,將session中任務的代理清空是一種防御性編程。
然后來看GET請求
- (NSURLSessionDataTask *)GET:(NSString *)URLString參數(shù):(ID)參數(shù)進度:(void(^)(NSProgress * _Nonnull))downloadProgress成功:(無效(^)(NSURLSessionDataTask * _Nonnull,id _Nullable))成功失敗:(void(^)(NSURLSessionDataTask * _Nullable,NSError * _Nonnull))失敗
{//生成一個任務
NSURLSessionDataTask * dataTask = [self dataTaskWithHTTPMethod:@“GET”URLString:URLString參數(shù):參數(shù)上傳進度:無downloadProgress:downloadProgress成功:成功失敗:失敗];
//開始網(wǎng)絡請求[dataTask resume];返回dataTask;}
這里主要生成一個NSURLSessionDataTask來進行網(wǎng)絡請求
我們繼續(xù)往父類里看,看看這個方法到底做了什么:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)方法URLString:(NSString *)URLString參數(shù):(ID)參數(shù)uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressdownloadProgress :(可空(void)(^)(NSProgress * downloadProgress))downloadProgress成功:(void(^)(NSURLSessionDataTask *,id))成功失敗:(void(^)(NSURLSessionDataTask *,NSError *))失敗{NSError * serializationError = nil;
//把參數(shù),還有各種東西轉(zhuǎn)化為一個請求
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和各種參數(shù)去獲取一個最終網(wǎng)絡請求需要的NSMutableURLRequest
2、調(diào)用另外一個方法dataTaskWithRequest去拿到我們需要NSURLSessionDataTask,并且在回掉中調(diào)用成功或失敗的回掉
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)方法URLString:(NSString *)URLString參數(shù):(ID)參數(shù)uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressdownloadProgress :(可空(void)(^)(NSProgress * downloadProgress))downloadProgress成功:(void(^)(NSURLSessionDataTask *,id))成功失敗:(void(^)(NSURLSessionDataTask *,NSError *))失敗{NSError * serializationError = nil;//把參數(shù),還有各種東西轉(zhuǎn)化為一個請求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;}
我們繼續(xù)往下看:當解析錯誤,我們直接調(diào)用failuer失敗模塊回去,里面有個self.completionQueue,這是我們自定義的,這個是gcd隊列,如果設置就從這個隊列中回掉了,不從主隊列中回掉了
實際上這個隊列還是很有用,有些公司有自己的一套數(shù)據(jù)加密解密解析模式,所以我們回掉過來的數(shù)據(jù)并不想在主隊列,我們可以在這個隊列對數(shù)據(jù)進行解析,然后在回到主線程中。
(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)請求uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressBlockdownloadProgress:(可空(void)(^)(NSProgress * downloadProgress))downloadProgressBlockcompletionHandler:(可空(void)(^)(NSURLResponse *響應,id _Nullable響應對象,NSError * _Nullable錯誤))completionHandler {__block NSURLSessionDataTask * dataTask = nil;//第一件事,創(chuàng)建NSURLSessionDataTask,里面適配了iOS8上以下taskIdentifiers,函數(shù)創(chuàng)建任務對象。//其實現(xiàn)應該是因為iOS 8.0以下版本中會并發(fā)地創(chuàng)建多個任務對象,而同步有沒有好,導致taskIdentifiers不唯一...這邊做了一個串行處理url_session_manager_create_task_safely(^ {dataTask = [self.session dataTaskWithRequest:request];});[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];返回dataTask;}
我們注意到這個方法非常簡單,就調(diào)用了一個url_session_manager_create_task_safely()函數(shù),傳了一個塊進去座里就是iOS的原生生成dataTask的方法。此外,還調(diào)用了一個addDelegateForDataTask的方法。
我們到這先到這個函數(shù)里去看看:
static void url_session_manager_create_task_safely(dispatch_block_t block){if(NSFoundationVersionNumber
方法非常簡單,關鍵是理解這么做的目的:為什么我們不直接去調(diào)用
dataTask = [self.session dataTaskWithRequest:request];
非要繞這么一圈,我們點進去錯誤日志里看看,原來這是為了適配iOS8上的以下,創(chuàng)建會話的時候,偶發(fā)的情況會出現(xiàn)會話的屬性taskIdentifier這個值不唯一,而這個taskIdentifier是我們后面來映射代表的關鍵,所以它必須是唯一的。
具體原因應該是NSURLSession內(nèi)部去生成任務的時候是用多線程并發(fā)去執(zhí)行的。想通了這一點,我們就很好解決了,我們只需要在iOS8上以下同步串行的去生成任務就可以防止這一問題發(fā)生(如果還是不理解同步串行的原因,可以看看注釋)。
題外話:很多同學都會抱怨為什么同步我從來用不到,看,有用到的地方了吧,很多東西不是沒用,而只是你想不到怎么用。
我們接著看到:
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
調(diào)用到:
- (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用來發(fā)送開始和掛起通知的時候會用到,就是用這個值來張貼通知,來兩者對應dataTask.taskDescription = self.taskDescriptionForSessionTasks;// *****將AF委托對象與dataTask建立關系[self setDelegate:delegate forTask:dataTask];//設置AF委托的上傳進度,下載進度塊。delegate.uploadProgressBlock = uploadProgressBlock;delegate.downloadProgressBlock = downloadProgressBlock;}
總結(jié)一下:
1)這個方法,生成了一個AFURLSessionManagerTaskDelegate,這個其實就是AF的自定義代理。我們請求傳來的參數(shù),都賦值給這個AF的代理了。
2)delegate.manager = self;代理把AFURLSessionManager這個類作為屬性了,我們可以看到:
@屬性(非原子,弱)AFURLSessionManager *管理器;
這個屬性是弱引用的,所以不會存在循環(huán)引用的問題。
3)我們調(diào)用了[self setDelegate:delegate forTask:dataTask];
我們進去看看這個方法做了什么:
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)委托forTask:(NSURLSessionTask *)任務{(diào)//斷言,如果沒有這個參數(shù),調(diào)試下墜毀在這NSParameterAssert(任務);NSParameterAssert(代表);//加鎖保證字典線程安全[self.lock lock];//將AF委托放入以taskIdentifier標記的詞典中(同一個NSURLSession中的taskIdentifier是唯一的)self.mutableTaskDelegatesKeyedByTaskIdentifier [@(task.taskIdentifier)] = delegate;//為AF代表設置任務的進度監(jiān)聽[委托setupProgressForTask:任務];//添加任務開始和暫停的通知[self addNotificationObserverForTask:task];[self.lock解鎖];}
這個方法主要就是把AF代理和任務建立映射,存在了一個我們事先聲明好的字典里。
而要加鎖的原因是因為本身我們這個字典屬性是可變的,是線程不安全的。而我們對這些方法的調(diào)用,確實是會在復雜的多線程環(huán)境中,后面會仔細提到線程問題。
還有個[delegate setupProgressForTask:task];我們到方法里去看看:
- (void)setupProgressForTask:(NSURLSessionTask *)task {__weak __typeof __(task)weakTask = task;//拿到上傳下載期望的數(shù)據(jù)大小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的一些屬性,并且把兩個和任務的任務狀態(tài)綁定在了一起。注意這兩個都是NSProgress的實例對象,(這里可能又一群小伙楞在這了,這是個什么...)簡單來說,這就是iOS7引進的一個用來管理進度的類,可以開始,暫停,取消,完整的對應了任務的各種狀態(tài),當進度進行各種操作的時候,任務也會引發(fā)對應操作。
2)給的任務和進度的各個屬及添加志愿監(jiān)聽,至于監(jiān)聽了干什么用,我們接著往下看:
- (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];}}//上面的賦新值會觸發(fā)這兩個,調(diào)用塊回調(diào),用戶拿到進度else if([object isEqual:self.downloadProgress]){if(self.downloadProgressBlock){self.downloadProgressBlock(對象);}}else if([object isEqual:self.uploadProgress]){if(self.uploadProgressBlock){self.uploadProgressBlock(對象);}}}
方法非常簡單直觀,主要就是如果任務觸發(fā)志愿,則給進度進度賦值,應為賦值了,所以會觸發(fā)進步的志愿,也會調(diào)用到這里,然后去執(zhí)行我們傳進來的downloadProgressBlock和uploadProgressBlock。主要的作用就是為了讓進度實時的傳遞。
主要是觀摩一下大神的寫代碼的結(jié)構(gòu),這個解耦的編程思想,不愧是大神...
還有一點需要注意:我們之前的setProgress和這個志愿監(jiān)聽,都是在我們AF自定義的委托內(nèi)的,是有一個任務就會有一個代表的所以說我們是每個任務都會去監(jiān)聽這些屬性,分別在各自的AF代理內(nèi)。看到這,可能有些小伙伴會有點亂,沒關系。等整個講完之后我們還會詳細的去講捋一捋經(jīng)理,任務,還有AF自定義代理三者之前的對應關系。
到這里我們整個對任務的處理就完成了。
2,HTTPS認證- (void)URLSession:(NSURLSession *)會話didReceiveChallenge:(NSURLAuthenticationChallenge *)挑戰(zhàn)completionHandler:(void(^)(NSURLSessionAuthChallengeDisposition disposition,NSURLCredential * credential))completionHandler{//挑戰(zhàn)處理類型為默認/ *NSURLSessionAuthChallengePerformDefaultHandling:默認方式處理NSURLSessionAuthChallengeUseCredential:使用指定的證書NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰(zhàn)* /NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;__block NSURLCredential * credential = nil;// sessionDidReceiveAuthenticationChallenge是自定義方法,用來如何應對服務器端的認證挑戰(zhàn)如果(self.sessionDidReceiveAuthenticationChallenge){disposition = self.sessionDidReceiveAuthenticationChallenge(session,challenge,&credential);} else {//此處服務器要求客戶端的接收認證挑戰(zhàn)方法是NSURLAuthenticationMethodServerTrust//也就是說服務端需要客戶端返回一個根據(jù)認證挑戰(zhàn)的保護空間提供的信任(即challenge.protectionSpace.serverTrust)產(chǎn)生的挑戰(zhàn)證書。//而這個證書就需要使用credentialForTrust:來創(chuàng)建一個NSURLCredential對象如果([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){//基于客戶端的安全策略來決定是否信任該服務器,不信任的話,也就沒必要響應挑戰(zhàn)如果([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]){//創(chuàng)建挑戰(zhàn)證書(注:挑戰(zhàn)方式為UseCredential和PerformDefaultHandling都需要新建挑戰(zhàn)證書)credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];//確定挑戰(zhàn)的方式如果(憑證){//證書挑戰(zhàn)處置= NSURLSessionAuthChallengeUseCredential;} else {//默認挑戰(zhàn)唯一區(qū)別,下面少了這一步!處置= NSURLSessionAuthChallengePerformDefaultHandling;}} else {//取消挑戰(zhàn)處置= NSURLSessionAuthChallengeCancelAuthenticationChallenge;}} else {//默認挑戰(zhàn)方式處置= NSURLSessionAuthChallengePerformDefaultHandling;}}//完成挑戰(zhàn)if(completionHandler){completionHandler(處置,憑證);}