AFURLSessionManager(二)

建議去看原文 ?AFURLSessionManager

_AFURLSessionTaskSwizzling

當時看這個私有類的時候一直想不通為什么要弄一個這樣的類呢?首先看了AFNetworking給出的解釋https://github.com/AFNetworking/AFNetworking/pull/2702大概說了當初這個私有類的由來,ios7和ios8 task的父類并不一樣,關鍵是resumeandsuspend這兩個方法的調用。

因此,AFNetworking 利用Runtime交換了resumeandsuspend的方法實現。在替換的方法中發送了狀態的通知。這個通知被使用在UIActivityIndicatorView+AFNetworking這個UIActivityIndicatorView的分類中。

方法的核心部分作用是層級遍歷父類,替換resumeandsuspend的實現方法。同時也解決了鎖死這個bug。

還有值得說的是+ (void)load這個方法,這個方法會在app啟動時加載所有類的時候調用,且只會調用一次,所以這就有了使用場景了,當想使用運行時做一些事情的時候,就能夠用上這個方法了。

舉幾個使用這個方法的例子:

SDAutoLayout

IQKeyBoardManager

UITableView+FDTemplateLayoutCell

MJExtension

下邊就看看代碼部分:

// 根據兩個方法名稱交換兩個方法,內部實現是先根據函數名獲取到對應方法實現// 再調用method_exchangeImplementations交換兩個方法staticinlinevoidaf_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector){? ? Method originalMethod = class_getInstanceMethod(theClass, originalSelector);? ? Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);? ? method_exchangeImplementations(originalMethod, swizzledMethod);}// 給theClass添加名為selector,對應實現為method的方法staticinlineBOOLaf_addMethod(Class theClass, SEL selector, Method method){// 內部實現使用的是class_addMethod方法,注意method_getTypeEncoding是為了獲得該方法的參數和返回類型returnclass_addMethod(theClass, selector,? method_getImplementation(method),? method_getTypeEncoding(method));}

--

+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {// 因為af_resume和af_suspend都是類的實例方法,所以使用class_getInstanceMethod獲取這兩個方法Method afResumeMethod = class_getInstanceMethod(self,@selector(af_resume));? ? Method afSuspendMethod = class_getInstanceMethod(self,@selector(af_suspend));// 給theClass添加一個名為af_resume的方法,使用@selector(af_resume)獲取方法名,使用afResumeMethod作為方法實現if(af_addMethod(theClass,@selector(af_resume), afResumeMethod)) {// 交換resume和af_resume的方法實現af_swizzleSelector(theClass,@selector(resume),@selector(af_resume));? ? }// 同上if(af_addMethod(theClass,@selector(af_suspend), afSuspendMethod)) {? ? ? ? af_swizzleSelector(theClass,@selector(suspend),@selector(af_suspend));? ? }}

--

- (NSURLSessionTaskState)state {NSAssert(NO,@"State method should never be called in the actual dummy class");// 初始狀態是NSURLSessionTaskStateCanceling;returnNSURLSessionTaskStateCanceling;}- (void)af_resume {NSAssert([selfrespondsToSelector:@selector(state)],@"Does not respond to state");NSURLSessionTaskStatestate = [selfstate];? ? [selfaf_resume];// 因為經過method swizzling后,此處的af_resume其實就是之前的resume,所以此處調用af_resume就是調用系統的resume。但是在程序中我們還是得使用resume,因為其實際調用的是af_resume// 如果之前是其他狀態,就變回resume狀態,此處會通知調用taskDidResumeif(state !=NSURLSessionTaskStateRunning) {? ? ? ? [[NSNotificationCenterdefaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotificationobject:self];? ? }}// 同上- (void)af_suspend {NSAssert([selfrespondsToSelector:@selector(state)],@"Does not respond to state");NSURLSessionTaskStatestate = [selfstate];? ? [selfaf_suspend];if(state !=NSURLSessionTaskStateSuspended) {? ? ? ? [[NSNotificationCenterdefaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotificationobject:self];? ? }}

--

+ (void)load {/**

WARNING: 高能預警

https://github.com/AFNetworking/AFNetworking/pull/2702

*/// 擔心以后iOS中不存在NSURLSessionTaskif(NSClassFromString(@"NSURLSessionTask")) {/**

iOS 7和iOS 8在NSURLSessionTask實現上有些許不同,這使得下面的代碼實現略顯trick

關于這個問題,大家做了很多Unit Test,足以證明這個方法是可行的

目前我們所知的:

- NSURLSessionTasks是一組class的統稱,如果你僅僅使用提供的API來獲取NSURLSessionTask的class,并不一定返回的是你想要的那個(獲取NSURLSessionTask的class目的是為了獲取其resume方法)

- 簡單地使用[NSURLSessionTask class]并不起作用。你需要新建一個NSURLSession,并根據創建的session再構建出一個NSURLSessionTask對象才行。

- iOS 7上,localDataTask(下面代碼構造出的NSURLSessionDataTask類型的變量,為了獲取對應Class)的類型是 __NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask,__NSCFLocalSessionTask繼承自__NSCFURLSessionTask。

- iOS 8上,localDataTask的類型為__NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask,__NSCFLocalSessionTask繼承自NSURLSessionTask

- iOS 7上,__NSCFLocalSessionTask和__NSCFURLSessionTask是僅有的兩個實現了resume和suspend方法的類,另外__NSCFLocalSessionTask中的resume和suspend并沒有調用其父類(即__NSCFURLSessionTask)方法,這也意味著兩個類的方法都需要進行method swizzling。

- iOS 8上,NSURLSessionTask是唯一實現了resume和suspend方法的類。這也意味著其是唯一需要進行method swizzling的類

- 因為NSURLSessionTask并不是在每個iOS版本中都存在,所以把這些放在此處(即load函數中),比如給一個dummy class添加swizzled方法都會變得很方便,管理起來也方便。

一些假設前提:

- 目前iOS中resume和suspend的方法實現中并沒有調用對應的父類方法。如果日后iOS改變了這種做法,我們還需要重新處理

- 沒有哪個后臺task會重寫resume和suspend函數

*/// 1) 首先構建一個NSURLSession對象session,再通過session構建出一個_NSCFLocalDataTask變量NSURLSessionConfiguration*configuration = [NSURLSessionConfigurationephemeralSessionConfiguration];NSURLSession* session = [NSURLSessionsessionWithConfiguration:configuration];#pragma GCC diagnostic push#pragma GCC diagnostic ignored"-Wnonnull"NSURLSessionDataTask*localDataTask = [session dataTaskWithURL:nil];#pragma clang diagnostic pop// 2) 獲取到af_resume實現的指針IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([selfclass],@selector(af_resume)));? ? ? ? Class currentClass = [localDataTask class];// 3) 檢查當前class是否實現了resume。如果實現了,繼續第4步。while(class_getInstanceMethod(currentClass,@selector(resume))) {// 4) 獲取到當前class的父類(superClass)Class superClass = [currentClass superclass];// 5) 獲取到當前class對于resume實現的指針IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass,@selector(resume)));//? 6) 獲取到父類對于resume實現的指針IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass,@selector(resume)));// 7) 如果當前class對于resume的實現和父類不一樣(類似iOS7上的情況),并且當前class的resume實現和af_resume不一樣,才進行method swizzling。if(classResumeIMP != superclassResumeIMP &&? ? ? ? ? ? ? ? originalAFResumeIMP != classResumeIMP) {? ? ? ? ? ? ? ? [selfswizzleResumeAndSuspendMethodForClass:currentClass];? ? ? ? ? ? }// 8) 設置當前操作的class為其父類class,重復步驟3~8currentClass = [currentClass superclass];? ? ? ? }? ? ? ? ? ? ? ? [localDataTask cancel];? ? ? ? [session finishTasksAndInvalidate];? ? }}

AFURLSessionManager

這個類的屬性我們就不解釋了,代碼也不貼上來了。我們來看看初始化方法中都設置了那些默認的值:

- (instancetype)init {return[selfinitWithSessionConfiguration:nil];}- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration*)configuration {self= [superinit];if(!self) {returnnil;? ? }if(!configuration) {? ? ? ? configuration = [NSURLSessionConfigurationdefaultSessionConfiguration];? ? }self.sessionConfiguration = configuration;self.operationQueue = [[NSOperationQueuealloc] init];self.operationQueue.maxConcurrentOperationCount =1;self.session = [NSURLSessionsessionWithConfiguration:self.sessionConfiguration delegate:selfdelegateQueue:self.operationQueue];self.responseSerializer = [AFJSONResponseSerializer serializer];self.securityPolicy = [AFSecurityPolicy defaultPolicy];#if !TARGET_OS_WATCHself.reachabilityManager = [AFNetworkReachabilityManager sharedManager];#endifself.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionaryalloc] init];self.lock = [[NSLockalloc] init];self.lock.name = AFURLSessionManagerLockName;? ? [self.session getTasksWithCompletionHandler:^(NSArray*dataTasks,NSArray*uploadTasks,NSArray*downloadTasks) {for(NSURLSessionDataTask*taskindataTasks) {? ? ? ? ? ? [selfaddDelegateForDataTask:task uploadProgress:nildownloadProgress:nilcompletionHandler:nil];? ? ? ? }for(NSURLSessionUploadTask*uploadTaskinuploadTasks) {? ? ? ? ? ? [selfaddDelegateForUploadTask:uploadTask progress:nilcompletionHandler:nil];? ? ? ? }for(NSURLSessionDownloadTask*downloadTaskindownloadTasks) {? ? ? ? ? ? [selfaddDelegateForDownloadTask:downloadTask progress:nildestination:nilcompletionHandler:nil];? ? ? ? }? ? }];returnself;}- (void)dealloc {? ? [[NSNotificationCenterdefaultCenter] removeObserver:self];}

可以看出默認創建一個NSOperationQueue且并發數為一個,默認的responseSerializer響應序列化為Json,默認的securityPolicy為defaultPolicy,同時添加reachabilityManager網絡監控對象。

- (NSString*)taskDescriptionForSessionTasks {return[NSStringstringWithFormat:@"%p",self];}

這個方法返回一個本類的地址,目的是通過這個字符串來判斷請求是不是來源于AFNetworkingAFNetworking在為每個task添加Delegate的時候,都會給task的taskDescription賦值為self.taskDescriptionForSessionTasks。在后邊的- (NSArray *)tasksForKeyPath:(NSString *)keyPath方法中會使用到這個字符串。

- (void)taskDidResume:(NSNotification*)notification {NSURLSessionTask*task = notification.object;if([task respondsToSelector:@selector(taskDescription)]) {if([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {dispatch_async(dispatch_get_main_queue(), ^{? ? ? ? ? ? ? ? [[NSNotificationCenterdefaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];? ? ? ? ? ? });? ? ? ? }? ? }}- (void)taskDidSuspend:(NSNotification*)notification {NSURLSessionTask*task = notification.object;if([task respondsToSelector:@selector(taskDescription)]) {if([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {dispatch_async(dispatch_get_main_queue(), ^{? ? ? ? ? ? ? ? [[NSNotificationCenterdefaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task];? ? ? ? ? ? });? ? ? ? }? ? }}

這兩個是通知方法,來源于下邊的兩個通知的監聽事件:

- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {? ? [[NSNotificationCenter defaultCenter]addObserver:selfselector:@selector(taskDidResume:)name:AFNSURLSessionTaskDidResumeNotificationobject:task];? ? [[NSNotificationCenter defaultCenter]addObserver:selfselector:@selector(taskDidSuspend:)name:AFNSURLSessionTaskDidSuspendNotificationobject:task];}- (void)removeNotificationObserverForTask:(NSURLSessionTask *)task {? ? [[NSNotificationCenter defaultCenter]removeObserver:selfname:AFNSURLSessionTaskDidSuspendNotificationobject:task];? ? [[NSNotificationCenter defaultCenter]removeObserver:selfname:AFNSURLSessionTaskDidResumeNotificationobject:task];}

還記得上邊提到的**_AFURLSessionTaskSwizzling**這個私有類嗎?它交換了resumeandsuspend這兩個方法,在方法中發了下邊兩個通知:

AFNSURLSessionTaskDidResumeNotification

AFNSURLSessionTaskDidSuspendNotification

接下來就是一個很巧妙的轉化過程了,按理說我們只需要接受并處理上邊的兩個通知不就可以了嗎? 但真實情況卻不是這樣的,并不是所有人使用網絡請求都是用AFNetworking,所以使用if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks])來做判斷,這個task是否來自AFNetworking

轉化后我們就是用下邊的通知,同時也是對外暴露出來的通知:

AFNetworkingTaskDidResumeNotification

AFNetworkingTaskDidSuspendNotification

- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {? ? NSParameterAssert(task);? ? AFURLSessionManagerTaskDelegate *delegate= nil;? ? [self.locklock];delegate= self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];? ? [self.lockunlock];returndelegate;}- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegateforTask:(NSURLSessionTask *)task{? ? NSParameterAssert(task);? ? NSParameterAssert(delegate);? ? [self.locklock];? ? self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] =delegate;? ? [delegatesetupProgressForTask:task];? ? [self addNotificationObserverForTask:task];? ? [self.lockunlock];}

這兩個方法是把AFURLSessionManagerTaskDelegate和task建立聯系。值得注意的是:

self.mutableTaskDelegatesKeyedByTaskIdentifier 這個字典以task.taskIdentifier為key,delegate為value。同事在讀取和設置的時候采用加鎖來保證安全。

在給task添加delegate的時候除了給self.mutableTaskDelegatesKeyedByTaskIdentifier賦值外,還需要設置delegate的ProgressForTask,且添加task的通知

--

- (void)addDelegateForDataTask:(NSURLSessionDataTask*)dataTask? ? ? ? ? ? ? ? uploadProgress:(nullablevoid(^)(NSProgress*uploadProgress)) uploadProgressBlock? ? ? ? ? ? ? downloadProgress:(nullablevoid(^)(NSProgress*downloadProgress)) downloadProgressBlock? ? ? ? ? ? completionHandler:(void(^)(NSURLResponse*response,idresponseObject,NSError*error))completionHandler{? ? AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];? ? delegate.manager =self;? ? delegate.completionHandler = completionHandler;? ? dataTask.taskDescription =self.taskDescriptionForSessionTasks;? ? [selfsetDelegate:delegate forTask:dataTask];? ? delegate.uploadProgressBlock = uploadProgressBlock;? ? delegate.downloadProgressBlock = downloadProgressBlock;}

給datatask添加delegate,AFNetworking中的每一個task肯定都有一個delegate。根據這個方法,我們可以看出給task添加代理的步驟為:

新建AFURLSessionManagerTaskDelegate

設置delegate

設置taskDescription

把taskdelegateAFURLSessionManager建立聯系

--

- (void)removeDelegateForTask:(NSURLSessionTask*)task {NSParameterAssert(task);? ? AFURLSessionManagerTaskDelegate *delegate = [selfdelegateForTask:task];? ? [self.lock lock];? ? [delegate cleanUpProgressForTask:task];? ? [selfremoveNotificationObserverForTask:task];? ? [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];? ? [self.lock unlock];}

- (NSArray*)tasksForKeyPath:(NSString*)keyPath {? ? __blockNSArray*tasks =nil;? ? dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);? ? [self.session getTasksWithCompletionHandler:^(NSArray*dataTasks,NSArray*uploadTasks,NSArray*downloadTasks) {if([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {? ? ? ? ? ? tasks = dataTasks;? ? ? ? }elseif([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {? ? ? ? ? ? tasks = uploadTasks;? ? ? ? }elseif([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {? ? ? ? ? ? tasks = downloadTasks;? ? ? ? }elseif([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {? ? ? ? ? ? tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];? ? ? ? }? ? ? ? dispatch_semaphore_signal(semaphore);? ? }];? ? dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);returntasks;}

getTasksWithCompletionHandler 這個方法是異步方法,上邊的方法中我們需要等待這個異步方法有結果后才能進行后邊的代碼。 我們就可以使用dispatch_semaphore_t 這個信號來實現異步等待。

具體過程如下:

新建一個信號

在異步方法中發送信號,也就說一旦我們得到了異步的結果,我們就發一個信號

等待信號,只有接收到指定的信號代碼才會往下走

這個信號的使用場景有很多,可以當安全鎖來使用,也可以像上邊一樣異步等待。 假如我們有這樣一個場景:我們有3個或者多個異步的網絡請求,必須等待所有的請求回來后,在使用這些請求的結果來做一些事情。那么該怎么辦呢? 解決方案就是:使用dispatch_group_t 和 dispatch_semaphore_t來實現。 在這里代碼就不貼出來了,有興趣的朋友而已自己google或者留言。

tasks= [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];

這么使用之前確實不太知道,如果是我,可能就直接賦值給數組了。那么@unionOfArrays.self又是什么意思呢?

@distinctUnionOfObjects 清楚重復值

unionOfObjects 保留重復值

--

- (NSArray*)tasks {return[selftasksForKeyPath:NSStringFromSelector(_cmd)];}- (NSArray*)dataTasks {return[selftasksForKeyPath:NSStringFromSelector(_cmd)];}- (NSArray*)uploadTasks {return[selftasksForKeyPath:NSStringFromSelector(_cmd)];}- (NSArray*)downloadTasks {return[selftasksForKeyPath:NSStringFromSelector(_cmd)];}

在oc中,當方法被編譯器轉換成objc_msgSend函數后,除了方法必須的參數,objc_msgSend還會接收兩個特殊的參數:receiver 與 selector。

objc_msgSend(receiver, selector, arg1, arg2, ...)

receiver 表示當前方法調用的類實例對象。

selector則表示當前方法所對應的selector。

這兩個參數是編譯器自動填充的,我們在調用方法時,不必在源代碼中顯示傳入,因此可以被看做是“隱式參數”。

如果想要在source code中獲取這兩個參數,則可以用self(當前類實例對象)和_cmd(當前調用方法的selector)來表示。

- (void)viewDidLoad{? ? [superviewDidLoad];NSLog(@"Current method: %@ %@",[selfclass],NSStringFromSelector(_cmd));}輸出結果為:TestingProject[570:11303] Current method: FirstViewController viewDidLoad

- (NSURLSessionDataTask*)dataTaskWithRequest:(NSURLRequest*)request? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uploadProgress:(nullablevoid(^)(NSProgress*uploadProgress)) uploadProgressBlock? ? ? ? ? ? ? ? ? ? ? ? ? ? downloadProgress:(nullablevoid(^)(NSProgress*downloadProgress)) downloadProgressBlock? ? ? ? ? ? ? ? ? ? ? ? ? ? completionHandler:(nullablevoid(^)(NSURLResponse*response,id_Nullable responseObject,NSError* _Nullable error))completionHandler {? ? __blockNSURLSessionDataTask*dataTask =nil;? ? url_session_manager_create_task_safely(^{? ? ? ? dataTask = [self.session dataTaskWithRequest:request];? ? });? ? [selfaddDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];returndataTask;}

這里大概說下幾種比較典型的創建task的方法,其他的方法就不做介紹了,原理大體相同。分為下邊兩個步驟:

創建task

給task添加Delegate

--

- (NSURLSessionUploadTask*)uploadTaskWithRequest:(NSURLRequest*)request? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? fromFile:(NSURL*)fileURL? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? progress:(void(^)(NSProgress*uploadProgress)) uploadProgressBlock? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? completionHandler:(void(^)(NSURLResponse*response,idresponseObject,NSError*error))completionHandler{? ? __blockNSURLSessionUploadTask*uploadTask =nil;? ? url_session_manager_create_task_safely(^{? ? ? ? uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];? ? });// 當uploadtTask創建失敗,且允許自動創建,會嘗試創建uploadtTaskif(!uploadTask &&self.attemptsToRecreateUploadTasksForBackgroundSessions &&self.session.configuration.identifier) {for(NSUIntegerattempts =0; !uploadTask && attempts < AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++) {? ? ? ? ? ? uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];? ? ? ? }? ? }? ? [selfaddDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];returnuploadTask;}

--

- (NSProgress*)uploadProgressForTask:(NSURLSessionTask*)task {return[[selfdelegateForTask:task] uploadProgress];}- (NSProgress*)downloadProgressForTask:(NSURLSessionTask*)task {return[[selfdelegateForTask:task] downloadProgress];}

- (NSString*)description {return[NSStringstringWithFormat:@"<%@: %p, session: %@, operationQueue: %@>",NSStringFromClass([selfclass]),self,self.session,self.operationQueue];}

假如我們自己寫了一個工具類,我們最好重寫description方法。

- (BOOL)respondsToSelector:(SEL)selector {if(selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {returnself.taskWillPerformHTTPRedirection !=nil;? ? }elseif(selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {returnself.dataTaskDidReceiveResponse !=nil;? ? }elseif(selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {returnself.dataTaskWillCacheResponse !=nil;? ? }elseif(selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {returnself.didFinishEventsForBackgroundURLSession !=nil;? ? }return[[selfclass]instancesRespondToSelector:selector];}

我們也可以使用respondsToSelector這個方法來攔截事件,把系統的事件和自定義的事件進行綁定。

NSURLSessionDelegate

// 這個方法是session收到的最后一條信息,- (void)URLSession:(NSURLSession*)sessiondidBecomeInvalidWithError:(NSError*)error{// 調用blockif(self.sessionDidBecomeInvalid) {self.sessionDidBecomeInvalid(session, error);? ? }// 發送通知[[NSNotificationCenterdefaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];}

--

- (void)URLSession:(NSURLSession*)sessiondidReceiveChallenge:(NSURLAuthenticationChallenge*)challenge completionHandler:(void(^)(NSURLSessionAuthChallengeDispositiondisposition,NSURLCredential*credential))completionHandler{// 創建默認的處理方式,PerformDefaultHandling方式將忽略credential這個參數NSURLSessionAuthChallengeDispositiondisposition =NSURLSessionAuthChallengePerformDefaultHandling;? ? __blockNSURLCredential*credential =nil;// 調動自身的處理方法,也就是說我們通過sessionDidReceiveAuthenticationChallenge這個block接收session,challenge 參數,返回一個NSURLSessionAuthChallengeDisposition結果,這個業務使我們自己在這個block中完成。if(self.sessionDidReceiveAuthenticationChallenge) {? ? ? ? disposition =self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);? ? }// 如果沒有實現自定義的驗證過程else{// 判斷challenge的authenticationMethodif([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {// 使用安全策略來驗證if([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {// 如果驗證通過,根據serverTrust創建依據credential = [NSURLCredentialcredentialForTrust:challenge.protectionSpace.serverTrust];if(credential) {// 有的話就返回UseCredentialdisposition =NSURLSessionAuthChallengeUseCredential;? ? ? ? ? ? ? ? }else{? ? ? ? ? ? ? ? ? ? disposition =NSURLSessionAuthChallengePerformDefaultHandling;? ? ? ? ? ? ? ? }? ? ? ? ? ? }else{// 驗證沒通過,返回CancelAuthenticationChallengedisposition =NSURLSessionAuthChallengeCancelAuthenticationChallenge;? ? ? ? ? ? }? ? ? ? }else{? ? ? ? ? ? disposition =NSURLSessionAuthChallengePerformDefaultHandling;? ? ? ? }? ? }if(completionHandler) {? ? ? ? completionHandler(disposition, credential);? ? }}

著重對這個方法介紹下。

點擊查看蘋果官方解釋

這個代理方法會在下邊兩種情況下被調用:

當遠程服務器要求客戶端提供證書或者Windows NT LAN Manager (NTLM)驗證

當session初次和服務器通過SSL或TSL建立連接,客戶端需要驗證服務端證書鏈

如果沒有實現這個方法,session就會調用delegate的URLSession:task:didReceiveChallenge:completionHandler:方法。

如果challenge.protectionSpace.authenticationMethod 在下邊4個中時,才會調用

NSURLAuthenticationMethodNTLM

NSURLAuthenticationMethodNegotiate 是否使用KerberosorNTLM驗證

NSURLAuthenticationMethodClientCertificate

NSURLAuthenticationMethodServerTrust

否則調用URLSession:task:didReceiveChallenge:completionHandler:方法。

NSURLSessionTaskDelegate

// 請求改變的時候調用- (void)URLSession:(NSURLSession*)session? ? ? ? ? ? ? task:(NSURLSessionTask*)taskwillPerformHTTPRedirection:(NSHTTPURLResponse*)response? ? ? ? newRequest:(NSURLRequest*)request completionHandler:(void(^)(NSURLRequest*))completionHandler{NSURLRequest*redirectRequest = request;if(self.taskWillPerformHTTPRedirection) {? ? ? ? redirectRequest =self.taskWillPerformHTTPRedirection(session, task, response, request);? ? }if(completionHandler) {? ? ? ? completionHandler(redirectRequest);? ? }}// 使用方法同 URLSession: didReceiveChallenge: completionHandler: 差不多- (void)URLSession:(NSURLSession*)session? ? ? ? ? ? ? task:(NSURLSessionTask*)taskdidReceiveChallenge:(NSURLAuthenticationChallenge*)challenge completionHandler:(void(^)(NSURLSessionAuthChallengeDispositiondisposition,NSURLCredential*credential))completionHandler{NSURLSessionAuthChallengeDispositiondisposition =NSURLSessionAuthChallengePerformDefaultHandling;? ? __blockNSURLCredential*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 = [NSURLCredentialcredentialForTrust:challenge.protectionSpace.serverTrust];? ? ? ? ? ? }else{? ? ? ? ? ? ? ? disposition =NSURLSessionAuthChallengeCancelAuthenticationChallenge;? ? ? ? ? ? }? ? ? ? }else{? ? ? ? ? ? disposition =NSURLSessionAuthChallengePerformDefaultHandling;? ? ? ? }? ? }if(completionHandler) {? ? ? ? completionHandler(disposition, credential);? ? }}// 請求需要一個全新的,未打開的數據時調用。特別是請求一個body失敗時,可以通過這個方法給一個新的body- (void)URLSession:(NSURLSession*)session? ? ? ? ? ? ? task:(NSURLSessionTask*)task needNewBodyStream:(void(^)(NSInputStream*bodyStream))completionHandler{NSInputStream*inputStream =nil;if(self.taskNeedNewBodyStream) {? ? ? ? inputStream =self.taskNeedNewBodyStream(session, task);? ? }elseif(task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]){? ? ? ? inputStream = [task.originalRequest.HTTPBodyStreamcopy];? ? }if(completionHandler) {? ? ? ? completionHandler(inputStream);? ? }}// 上傳數據時候調用- (void)URLSession:(NSURLSession*)session? ? ? ? ? ? ? task:(NSURLSessionTask*)task? didSendBodyData:(int64_t)bytesSent? ? totalBytesSent:(int64_t)totalBytesSenttotalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{? ? int64_t totalUnitCount = totalBytesExpectedToSend;if(totalUnitCount ==NSURLSessionTransferSizeUnknown) {NSString*contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];if(contentLength) {? ? ? ? ? ? totalUnitCount = (int64_t) [contentLength longLongValue];? ? ? ? }? ? }if(self.taskDidSendBodyData) {self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);? ? }}// 完成時調用- (void)URLSession:(NSURLSession*)session? ? ? ? ? ? ? task:(NSURLSessionTask*)taskdidCompleteWithError:(NSError*)error{? ? AFURLSessionManagerTaskDelegate *delegate = [selfdelegateForTask:task];// delegate may be nil when completing a task in the backgroundif(delegate) {? ? ? ? [delegate URLSession:session task:task didCompleteWithError:error];? ? ? ? [selfremoveDelegateForTask:task];? ? }if(self.taskDidComplete) {self.taskDidComplete(session, task, error);? ? }}

NSURLSessionDataDelegate

// 收到響應時調用- (void)URLSession:(NSURLSession*)session? ? ? ? ? dataTask:(NSURLSessionDataTask*)dataTaskdidReceiveResponse:(NSURLResponse*)response completionHandler:(void(^)(NSURLSessionResponseDispositiondisposition))completionHandler{NSURLSessionResponseDispositiondisposition =NSURLSessionResponseAllow;if(self.dataTaskDidReceiveResponse) {? ? ? ? disposition =self.dataTaskDidReceiveResponse(session, dataTask, response);? ? }if(completionHandler) {? ? ? ? completionHandler(disposition);? ? }}//? 當NSURLSessionDataTask變為NSURLSessionDownloadTask調用,之后NSURLSessionDataTask將不再接受消息- (void)URLSession:(NSURLSession*)session? ? ? ? ? dataTask:(NSURLSessionDataTask*)dataTaskdidBecomeDownloadTask:(NSURLSessionDownloadTask*)downloadTask{? ? AFURLSessionManagerTaskDelegate *delegate = [selfdelegateForTask:dataTask];if(delegate) {? ? ? ? [selfremoveDelegateForTask:dataTask];// 重新設置代理[selfsetDelegate:delegate forTask:downloadTask];? ? }if(self.dataTaskDidBecomeDownloadTask) {self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);? ? }}// 接受數據過程中,調用,只限于NSURLSessionDataTask- (void)URLSession:(NSURLSession*)session? ? ? ? ? dataTask:(NSURLSessionDataTask*)dataTask? ? didReceiveData:(NSData*)data{? ? AFURLSessionManagerTaskDelegate *delegate = [selfdelegateForTask:dataTask];? ? [delegate URLSession:session dataTask:dataTask didReceiveData:data];if(self.dataTaskDidReceiveData) {self.dataTaskDidReceiveData(session, dataTask, data);? ? }}// 即將緩存響應時調用- (void)URLSession:(NSURLSession*)session? ? ? ? ? dataTask:(NSURLSessionDataTask*)dataTask willCacheResponse:(NSCachedURLResponse*)proposedResponse completionHandler:(void(^)(NSCachedURLResponse*cachedResponse))completionHandler{NSCachedURLResponse*cachedResponse = proposedResponse;if(self.dataTaskWillCacheResponse) {? ? ? ? cachedResponse =self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);? ? }if(completionHandler) {? ? ? ? completionHandler(cachedResponse);? ? }}// 后臺任務完成成后- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession*)session {if(self.didFinishEventsForBackgroundURLSession) {dispatch_async(dispatch_get_main_queue(), ^{self.didFinishEventsForBackgroundURLSession(session);? ? ? ? });? ? }}

NSURLSessionDownloadDelegate

// 下載完成后調用- (void)URLSession:(NSURLSession*)session? ? ? downloadTask:(NSURLSessionDownloadTask*)downloadTaskdidFinishDownloadingToURL:(NSURL*)location{? ? AFURLSessionManagerTaskDelegate *delegate = [selfdelegateForTask:downloadTask];if(self.downloadTaskDidFinishDownloading) {NSURL*fileURL =self.downloadTaskDidFinishDownloading(session, downloadTask, location);if(fileURL) {? ? ? ? ? ? delegate.downloadFileURL = fileURL;NSError*error =nil;? ? ? ? ? ? [[NSFileManagerdefaultManager] moveItemAtURL:location toURL:fileURL error:&error];if(error) {? ? ? ? ? ? ? ? [[NSNotificationCenterdefaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];? ? ? ? ? ? }return;? ? ? ? }? ? }if(delegate) {? ? ? ? [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];? ? }}// 下載中調用- (void)URLSession:(NSURLSession*)session? ? ? downloadTask:(NSURLSessionDownloadTask*)downloadTask? ? ? didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWrittentotalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{if(self.downloadTaskDidWriteData) {self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);? ? }}// 回復下載時調用,使用fileOffset實現- (void)URLSession:(NSURLSession*)session? ? ? downloadTask:(NSURLSessionDownloadTask*)downloadTask didResumeAtOffset:(int64_t)fileOffsetexpectedTotalBytes:(int64_t)expectedTotalBytes{if(self.downloadTaskDidResume) {self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);? ? }}

** 好了,這篇文章就到此為之了,到目前位置,AFNetworking已經解讀了5篇了,所有的核心類也解釋完畢,下一篇文章會是AFHTTPSessionManager這個類了 。我們最終的目標是寫一個通用的包含大部分功能的網絡框架,這個需要在解讀完剩余的類之后再實現。我會演示一個從無到有的網絡框架的產生過程。**

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容