AFURLSessionManager(二)

建議去看原文 ?AFURLSessionManager

_AFURLSessionTaskSwizzling

當(dāng)時(shí)看這個(gè)私有類的時(shí)候一直想不通為什么要弄一個(gè)這樣的類呢瘟则?首先看了AFNetworking給出的解釋https://github.com/AFNetworking/AFNetworking/pull/2702大概說(shuō)了當(dāng)初這個(gè)私有類的由來(lái)捐顷,ios7和ios8 task的父類并不一樣驶赏,關(guān)鍵是resumeandsuspend這兩個(gè)方法的調(diào)用。

因此,AFNetworking 利用Runtime交換了resumeandsuspend的方法實(shí)現(xiàn)。在替換的方法中發(fā)送了狀態(tài)的通知锋谐。這個(gè)通知被使用在UIActivityIndicatorView+AFNetworking這個(gè)UIActivityIndicatorView的分類中。

方法的核心部分作用是層級(jí)遍歷父類截酷,替換resumeandsuspend的實(shí)現(xiàn)方法涮拗。同時(shí)也解決了鎖死這個(gè)bug。

還有值得說(shuō)的是+ (void)load這個(gè)方法迂苛,這個(gè)方法會(huì)在app啟動(dòng)時(shí)加載所有類的時(shí)候調(diào)用三热,且只會(huì)調(diào)用一次,所以這就有了使用場(chǎng)景了,當(dāng)想使用運(yùn)行時(shí)做一些事情的時(shí)候三幻,就能夠用上這個(gè)方法了康铭。

舉幾個(gè)使用這個(gè)方法的例子:

SDAutoLayout

IQKeyBoardManager

UITableView+FDTemplateLayoutCell

MJExtension

下邊就看看代碼部分:

// 根據(jù)兩個(gè)方法名稱交換兩個(gè)方法,內(nèi)部實(shí)現(xiàn)是先根據(jù)函數(shù)名獲取到對(duì)應(yīng)方法實(shí)現(xiàn)// 再調(diào)用method_exchangeImplementations交換兩個(gè)方法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赌髓,對(duì)應(yīng)實(shí)現(xiàn)為method的方法staticinlineBOOLaf_addMethod(Class theClass, SEL selector, Method method){// 內(nèi)部實(shí)現(xiàn)使用的是class_addMethod方法从藤,注意method_getTypeEncoding是為了獲得該方法的參數(shù)和返回類型returnclass_addMethod(theClass, selector,? method_getImplementation(method),? method_getTypeEncoding(method));}

--

+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {// 因?yàn)閍f_resume和af_suspend都是類的實(shí)例方法,所以使用class_getInstanceMethod獲取這兩個(gè)方法Method afResumeMethod = class_getInstanceMethod(self,@selector(af_resume));? ? Method afSuspendMethod = class_getInstanceMethod(self,@selector(af_suspend));// 給theClass添加一個(gè)名為af_resume的方法锁蠕,使用@selector(af_resume)獲取方法名夷野,使用afResumeMethod作為方法實(shí)現(xiàn)if(af_addMethod(theClass,@selector(af_resume), afResumeMethod)) {// 交換resume和af_resume的方法實(shí)現(xiàn)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");// 初始狀態(tài)是NSURLSessionTaskStateCanceling;returnNSURLSessionTaskStateCanceling;}- (void)af_resume {NSAssert([selfrespondsToSelector:@selector(state)],@"Does not respond to state");NSURLSessionTaskStatestate = [selfstate];? ? [selfaf_resume];// 因?yàn)榻?jīng)過(guò)method swizzling后,此處的af_resume其實(shí)就是之前的resume荣倾,所以此處調(diào)用af_resume就是調(diào)用系統(tǒng)的resume悯搔。但是在程序中我們還是得使用resume,因?yàn)槠鋵?shí)際調(diào)用的是af_resume// 如果之前是其他狀態(tài)舌仍,就變回resume狀態(tài)妒貌,此處會(huì)通知調(diào)用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: 高能預(yù)警

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

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

iOS 7和iOS 8在NSURLSessionTask實(shí)現(xiàn)上有些許不同通危,這使得下面的代碼實(shí)現(xiàn)略顯trick

關(guān)于這個(gè)問(wèn)題,大家做了很多Unit Test灌曙,足以證明這個(gè)方法是可行的

目前我們所知的:

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

- 簡(jiǎn)單地使用[NSURLSessionTask class]并不起作用在刺。你需要新建一個(gè)NSURLSession逆害,并根據(jù)創(chuàng)建的session再構(gòu)建出一個(gè)NSURLSessionTask對(duì)象才行。

- iOS 7上蚣驼,localDataTask(下面代碼構(gòu)造出的NSURLSessionDataTask類型的變量魄幕,為了獲取對(duì)應(yīng)Class)的類型是 __NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask颖杏,__NSCFLocalSessionTask繼承自__NSCFURLSessionTask纯陨。

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

- iOS 7上,__NSCFLocalSessionTask和__NSCFURLSessionTask是僅有的兩個(gè)實(shí)現(xiàn)了resume和suspend方法的類欲鹏,另外__NSCFLocalSessionTask中的resume和suspend并沒有調(diào)用其父類(即__NSCFURLSessionTask)方法,這也意味著兩個(gè)類的方法都需要進(jìn)行method swizzling臭墨。

- iOS 8上赔嚎,NSURLSessionTask是唯一實(shí)現(xiàn)了resume和suspend方法的類。這也意味著其是唯一需要進(jìn)行method swizzling的類

- 因?yàn)镹SURLSessionTask并不是在每個(gè)iOS版本中都存在胧弛,所以把這些放在此處(即load函數(shù)中)尤误,比如給一個(gè)dummy class添加swizzled方法都會(huì)變得很方便,管理起來(lái)也方便结缚。

一些假設(shè)前提:

- 目前iOS中resume和suspend的方法實(shí)現(xiàn)中并沒有調(diào)用對(duì)應(yīng)的父類方法损晤。如果日后iOS改變了這種做法,我們還需要重新處理

- 沒有哪個(gè)后臺(tái)task會(huì)重寫resume和suspend函數(shù)

*/// 1) 首先構(gòu)建一個(gè)NSURLSession對(duì)象session红竭,再通過(guò)session構(gòu)建出一個(gè)_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實(shí)現(xiàn)的指針I(yè)MP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([selfclass],@selector(af_resume)));? ? ? ? Class currentClass = [localDataTask class];// 3) 檢查當(dāng)前class是否實(shí)現(xiàn)了resume尤勋。如果實(shí)現(xiàn)了,繼續(xù)第4步茵宪。while(class_getInstanceMethod(currentClass,@selector(resume))) {// 4) 獲取到當(dāng)前class的父類(superClass)Class superClass = [currentClass superclass];// 5) 獲取到當(dāng)前class對(duì)于resume實(shí)現(xiàn)的指針I(yè)MP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass,@selector(resume)));//? 6) 獲取到父類對(duì)于resume實(shí)現(xiàn)的指針I(yè)MP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass,@selector(resume)));// 7) 如果當(dāng)前class對(duì)于resume的實(shí)現(xiàn)和父類不一樣(類似iOS7上的情況)最冰,并且當(dāng)前class的resume實(shí)現(xiàn)和af_resume不一樣,才進(jìn)行method swizzling稀火。if(classResumeIMP != superclassResumeIMP &&? ? ? ? ? ? ? ? originalAFResumeIMP != classResumeIMP) {? ? ? ? ? ? ? ? [selfswizzleResumeAndSuspendMethodForClass:currentClass];? ? ? ? ? ? }// 8) 設(shè)置當(dāng)前操作的class為其父類class暖哨,重復(fù)步驟3~8currentClass = [currentClass superclass];? ? ? ? }? ? ? ? ? ? ? ? [localDataTask cancel];? ? ? ? [session finishTasksAndInvalidate];? ? }}

AFURLSessionManager

這個(gè)類的屬性我們就不解釋了,代碼也不貼上來(lái)了凰狞。我們來(lái)看看初始化方法中都設(shè)置了那些默認(rèn)的值:

- (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];}

可以看出默認(rèn)創(chuàng)建一個(gè)NSOperationQueue且并發(fā)數(shù)為一個(gè)篇裁,默認(rèn)的responseSerializer響應(yīng)序列化為Json沛慢,默認(rèn)的securityPolicy為defaultPolicy,同時(shí)添加reachabilityManager網(wǎng)絡(luò)監(jiān)控對(duì)象达布。

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

這個(gè)方法返回一個(gè)本類的地址团甲,目的是通過(guò)這個(gè)字符串來(lái)判斷請(qǐng)求是不是來(lái)源于AFNetworkingAFNetworking在為每個(gè)task添加Delegate的時(shí)候往枣,都會(huì)給task的taskDescription賦值為self.taskDescriptionForSessionTasks伐庭。在后邊的- (NSArray *)tasksForKeyPath:(NSString *)keyPath方法中會(huì)使用到這個(gè)字符串。

- (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];? ? ? ? ? ? });? ? ? ? }? ? }}

這兩個(gè)是通知方法分冈,來(lái)源于下邊的兩個(gè)通知的監(jiān)聽事件:

- (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**這個(gè)私有類嗎圾另?它交換了resumeandsuspend這兩個(gè)方法,在方法中發(fā)了下邊兩個(gè)通知:

AFNSURLSessionTaskDidResumeNotification

AFNSURLSessionTaskDidSuspendNotification

接下來(lái)就是一個(gè)很巧妙的轉(zhuǎn)化過(guò)程了雕沉,按理說(shuō)我們只需要接受并處理上邊的兩個(gè)通知不就可以了嗎集乔? 但真實(shí)情況卻不是這樣的,并不是所有人使用網(wǎng)絡(luò)請(qǐng)求都是用AFNetworking坡椒,所以使用if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks])來(lái)做判斷扰路,這個(gè)task是否來(lái)自AFNetworking

轉(zhuǎn)化后我們就是用下邊的通知倔叼,同時(shí)也是對(duì)外暴露出來(lái)的通知:

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];}

這兩個(gè)方法是把AFURLSessionManagerTaskDelegate和task建立聯(lián)系汗唱。值得注意的是:

self.mutableTaskDelegatesKeyedByTaskIdentifier 這個(gè)字典以task.taskIdentifier為key,delegate為value丈攒。同事在讀取和設(shè)置的時(shí)候采用加鎖來(lái)保證安全哩罪。

在給task添加delegate的時(shí)候除了給self.mutableTaskDelegatesKeyedByTaskIdentifier賦值外,還需要設(shè)置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中的每一個(gè)task肯定都有一個(gè)delegate显设。根據(jù)這個(gè)方法框弛,我們可以看出給task添加代理的步驟為:

新建AFURLSessionManagerTaskDelegate

設(shè)置delegate

設(shè)置taskDescription

把taskdelegateAFURLSessionManager建立聯(lián)系

--

- (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 這個(gè)方法是異步方法,上邊的方法中我們需要等待這個(gè)異步方法有結(jié)果后才能進(jìn)行后邊的代碼捕捂。 我們就可以使用dispatch_semaphore_t 這個(gè)信號(hào)來(lái)實(shí)現(xiàn)異步等待瑟枫。

具體過(guò)程如下:

新建一個(gè)信號(hào)

在異步方法中發(fā)送信號(hào),也就說(shuō)一旦我們得到了異步的結(jié)果指攒,我們就發(fā)一個(gè)信號(hào)

等待信號(hào)力奋,只有接收到指定的信號(hào)代碼才會(huì)往下走

這個(gè)信號(hào)的使用場(chǎng)景有很多,可以當(dāng)安全鎖來(lái)使用幽七,也可以像上邊一樣異步等待景殷。 假如我們有這樣一個(gè)場(chǎng)景:我們有3個(gè)或者多個(gè)異步的網(wǎng)絡(luò)請(qǐng)求,必須等待所有的請(qǐng)求回來(lái)后,在使用這些請(qǐng)求的結(jié)果來(lái)做一些事情猿挚。那么該怎么辦呢咐旧? 解決方案就是:使用dispatch_group_t 和 dispatch_semaphore_t來(lái)實(shí)現(xiàn)。 在這里代碼就不貼出來(lái)了绩蜻,有興趣的朋友而已自己google或者留言铣墨。

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

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

@distinctUnionOfObjects 清楚重復(fù)值

unionOfObjects 保留重復(fù)值

--

- (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中孕蝉,當(dāng)方法被編譯器轉(zhuǎn)換成objc_msgSend函數(shù)后屡律,除了方法必須的參數(shù),objc_msgSend還會(huì)接收兩個(gè)特殊的參數(shù):receiver 與 selector降淮。

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

receiver 表示當(dāng)前方法調(diào)用的類實(shí)例對(duì)象超埋。

selector則表示當(dāng)前方法所對(duì)應(yīng)的selector。

這兩個(gè)參數(shù)是編譯器自動(dòng)填充的佳鳖,我們?cè)谡{(diào)用方法時(shí)霍殴,不必在源代碼中顯示傳入,因此可以被看做是“隱式參數(shù)”系吩。

如果想要在source code中獲取這兩個(gè)參數(shù)来庭,則可以用self(當(dāng)前類實(shí)例對(duì)象)和_cmd(當(dāng)前調(diào)用方法的selector)來(lái)表示。

- (void)viewDidLoad{? ? [superviewDidLoad];NSLog(@"Current method: %@ %@",[selfclass],NSStringFromSelector(_cmd));}輸出結(jié)果為: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;}

這里大概說(shuō)下幾種比較典型的創(chuàng)建task的方法穿挨,其他的方法就不做介紹了月弛,原理大體相同。分為下邊兩個(gè)步驟:

創(chuàng)建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];? ? });// 當(dāng)uploadtTask創(chuàng)建失敗絮蒿,且允許自動(dòng)創(chuàng)建,會(huì)嘗試創(chuàng)建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];}

假如我們自己寫了一個(gè)工具類叁鉴,我們最好重寫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這個(gè)方法來(lái)攔截事件,把系統(tǒng)的事件和自定義的事件進(jìn)行綁定幌墓。

NSURLSessionDelegate

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

--

- (void)URLSession:(NSURLSession*)sessiondidReceiveChallenge:(NSURLAuthenticationChallenge*)challenge completionHandler:(void(^)(NSURLSessionAuthChallengeDispositiondisposition,NSURLCredential*credential))completionHandler{// 創(chuàng)建默認(rèn)的處理方式,PerformDefaultHandling方式將忽略credential這個(gè)參數(shù)NSURLSessionAuthChallengeDispositiondisposition =NSURLSessionAuthChallengePerformDefaultHandling;? ? __blockNSURLCredential*credential =nil;// 調(diào)動(dòng)自身的處理方法常侣,也就是說(shuō)我們通過(guò)sessionDidReceiveAuthenticationChallenge這個(gè)block接收session蜡饵,challenge 參數(shù),返回一個(gè)NSURLSessionAuthChallengeDisposition結(jié)果胳施,這個(gè)業(yè)務(wù)使我們自己在這個(gè)block中完成溯祸。if(self.sessionDidReceiveAuthenticationChallenge) {? ? ? ? disposition =self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);? ? }// 如果沒有實(shí)現(xiàn)自定義的驗(yàn)證過(guò)程else{// 判斷challenge的authenticationMethodif([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {// 使用安全策略來(lái)驗(yàn)證if([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {// 如果驗(yàn)證通過(guò),根據(jù)serverTrust創(chuàng)建依據(jù)credential = [NSURLCredentialcredentialForTrust:challenge.protectionSpace.serverTrust];if(credential) {// 有的話就返回UseCredentialdisposition =NSURLSessionAuthChallengeUseCredential;? ? ? ? ? ? ? ? }else{? ? ? ? ? ? ? ? ? ? disposition =NSURLSessionAuthChallengePerformDefaultHandling;? ? ? ? ? ? ? ? }? ? ? ? ? ? }else{// 驗(yàn)證沒通過(guò),返回CancelAuthenticationChallengedisposition =NSURLSessionAuthChallengeCancelAuthenticationChallenge;? ? ? ? ? ? }? ? ? ? }else{? ? ? ? ? ? disposition =NSURLSessionAuthChallengePerformDefaultHandling;? ? ? ? }? ? }if(completionHandler) {? ? ? ? completionHandler(disposition, credential);? ? }}

著重對(duì)這個(gè)方法介紹下焦辅。

點(diǎn)擊查看蘋果官方解釋

這個(gè)代理方法會(huì)在下邊兩種情況下被調(diào)用:

當(dāng)遠(yuǎn)程服務(wù)器要求客戶端提供證書或者Windows NT LAN Manager (NTLM)驗(yàn)證

當(dāng)session初次和服務(wù)器通過(guò)SSL或TSL建立連接博杖,客戶端需要驗(yàn)證服務(wù)端證書鏈

如果沒有實(shí)現(xiàn)這個(gè)方法,session就會(huì)調(diào)用delegate的URLSession:task:didReceiveChallenge:completionHandler:方法筷登。

如果challenge.protectionSpace.authenticationMethod 在下邊4個(gè)中時(shí)剃根,才會(huì)調(diào)用

NSURLAuthenticationMethodNTLM

NSURLAuthenticationMethodNegotiate 是否使用KerberosorNTLM驗(yàn)證

NSURLAuthenticationMethodClientCertificate

NSURLAuthenticationMethodServerTrust

否則調(diào)用URLSession:task:didReceiveChallenge:completionHandler:方法。

NSURLSessionTaskDelegate

// 請(qǐng)求改變的時(shí)候調(diào)用- (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);? ? }}// 請(qǐng)求需要一個(gè)全新的前方,未打開的數(shù)據(jù)時(shí)調(diào)用狈醉。特別是請(qǐng)求一個(gè)body失敗時(shí),可以通過(guò)這個(gè)方法給一個(gè)新的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);? ? }}// 上傳數(shù)據(jù)時(shí)候調(diào)用- (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);? ? }}// 完成時(shí)調(diào)用- (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

// 收到響應(yīng)時(shí)調(diào)用- (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);? ? }}//? 當(dāng)NSURLSessionDataTask變?yōu)镹SURLSessionDownloadTask調(diào)用惠险,之后NSURLSessionDataTask將不再接受消息- (void)URLSession:(NSURLSession*)session? ? ? ? ? dataTask:(NSURLSessionDataTask*)dataTaskdidBecomeDownloadTask:(NSURLSessionDownloadTask*)downloadTask{? ? AFURLSessionManagerTaskDelegate *delegate = [selfdelegateForTask:dataTask];if(delegate) {? ? ? ? [selfremoveDelegateForTask:dataTask];// 重新設(shè)置代理[selfsetDelegate:delegate forTask:downloadTask];? ? }if(self.dataTaskDidBecomeDownloadTask) {self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);? ? }}// 接受數(shù)據(jù)過(guò)程中苗傅,調(diào)用,只限于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);? ? }}// 即將緩存響應(yīng)時(shí)調(diào)用- (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);? ? }}// 后臺(tái)任務(wù)完成成后- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession*)session {if(self.didFinishEventsForBackgroundURLSession) {dispatch_async(dispatch_get_main_queue(), ^{self.didFinishEventsForBackgroundURLSession(session);? ? ? ? });? ? }}

NSURLSessionDownloadDelegate

// 下載完成后調(diào)用- (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];? ? }}// 下載中調(diào)用- (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);? ? }}// 回復(fù)下載時(shí)調(diào)用莺匠,使用fileOffset實(shí)現(xiàn)- (void)URLSession:(NSURLSession*)session? ? ? downloadTask:(NSURLSessionDownloadTask*)downloadTask didResumeAtOffset:(int64_t)fileOffsetexpectedTotalBytes:(int64_t)expectedTotalBytes{if(self.downloadTaskDidResume) {self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);? ? }}

** 好了金吗,這篇文章就到此為之了,到目前位置趣竣,AFNetworking已經(jīng)解讀了5篇了摇庙,所有的核心類也解釋完畢,下一篇文章會(huì)是AFHTTPSessionManager這個(gè)類了 遥缕。我們最終的目標(biāo)是寫一個(gè)通用的包含大部分功能的網(wǎng)絡(luò)框架卫袒,這個(gè)需要在解讀完剩余的類之后再實(shí)現(xiàn)。我會(huì)演示一個(gè)從無(wú)到有的網(wǎng)絡(luò)框架的產(chǎn)生過(guò)程单匣。**

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末夕凝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子户秤,更是在濱河造成了極大的恐慌码秉,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸡号,死亡現(xiàn)場(chǎng)離奇詭異转砖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鲸伴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門府蔗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人汞窗,你說(shuō)我怎么就攤上這事姓赤。” “怎么了仲吏?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵不铆,是天一觀的道長(zhǎng)蝌焚。 經(jīng)常有香客問(wèn)我,道長(zhǎng)狂男,這世上最難降的妖魔是什么综看? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮岖食,結(jié)果婚禮上红碑,老公的妹妹穿的比我還像新娘。我一直安慰自己泡垃,他們只是感情好析珊,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蔑穴,像睡著了一般忠寻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上存和,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天奕剃,我揣著相機(jī)與錄音,去河邊找鬼捐腿。 笑死纵朋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的茄袖。 我是一名探鬼主播操软,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宪祥!你這毒婦竟也來(lái)了聂薪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蝗羊,失蹤者是張志新(化名)和其女友劉穎藏澳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耀找,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翔悠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涯呻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凉驻。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腻要,死狀恐怖复罐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雄家,我是刑警寧澤效诅,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響乱投,放射性物質(zhì)發(fā)生泄漏咽笼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一戚炫、第九天 我趴在偏房一處隱蔽的房頂上張望剑刑。 院中可真熱鬧,春花似錦双肤、人聲如沸施掏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)七芭。三九已至,卻和暖如春蔑赘,著一層夾襖步出監(jiān)牢的瞬間狸驳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工缩赛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耙箍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓峦筒,卻偏偏與公主長(zhǎng)得像究西,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子物喷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容