解讀AF之AFURLSessionManager

介紹

上篇文章有提到AFURLSessionManager這個(gè)類,在AF中這個(gè)類是對(duì)NSURLSession進(jìn)行的封裝,在使用NSURLSession需要知道使用套路。

  1. 首先創(chuàng)建會(huì)話
  2. 通過(guò)會(huì)話和請(qǐng)求獲取任務(wù)。
  3. 每一個(gè)網(wǎng)絡(luò)請(qǐng)求被封裝成一個(gè)任務(wù)。
  4. 通過(guò)任務(wù)進(jìn)行網(wǎng)絡(luò)通信。

通過(guò)上述的介紹,有兩個(gè)關(guān)鍵詞,會(huì)話、請(qǐng)求、任務(wù)
會(huì)話NSURLSession(通過(guò)會(huì)話我們可以獲取對(duì)應(yīng)的請(qǐng)求任務(wù)).
請(qǐng)求NSURLRequest.
任務(wù)NSURLSessionDataTask、NSURLSessionUploadTask
、NSURLSessionDownloadTaskNSURLSessionStreamTask.

任務(wù)有四種,分別是數(shù)據(jù)任務(wù),上傳任務(wù),下載任務(wù),流式任務(wù)。

他們的繼承關(guān)系:
NSURLSessionDataTask: NSURLSessionTask
NSURLSessionUploadTask : NSURLSessionDataTask
NSURLSessionDownloadTask : NSURLSessionTask
他們的代理關(guān)系:
NSURLSessionTaskDelegate <NSURLSessionDelegate>
NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>

AFURLSessionManager

AFURLSessionManager類文件中我們可以看到其中還包含了AFURLSessionManagerTaskDelegate_AFURLSessionTaskSwizzling.這兩個(gè)類。

AFURLSessionManagerTaskDelegate

AFURLSessionManagerTaskDelegate是對(duì)請(qǐng)求任務(wù)的另一層封裝。主要處理請(qǐng)求數(shù)據(jù)相關(guān)業(yè)務(wù),比如說(shuō)上傳下載的進(jìn)度、下載獲取的數(shù)據(jù)、下載完成的回調(diào)等。AFURLSessionManagerTaskDelegateAFURLSessionManager的關(guān)系,請(qǐng)看下圖。

Snip20190220_2.png

AFURLSessionManagerTaskDelegate類中也實(shí)現(xiàn)了NSURLSession的相關(guān)代理方法。

#pragma mark - NSURLSessionTaskDelegate

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics 

#pragma mark - NSURLSessionDataDelegate

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data


- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend

#pragma mark - NSURLSessionDownloadDelegate

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location

上述這里代理方法的調(diào)用,并不是系統(tǒng)調(diào)用,而是通過(guò)AFURLSessionManager中的回調(diào)方法,和 AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
通過(guò)對(duì)象主動(dòng)調(diào)用,處理內(nèi)部數(shù)據(jù)。

_AFURLSessionTaskSwizzling

這個(gè)私有的內(nèi)部類主要解決iOS 7和iOS 8在NSURLSessionTask實(shí)現(xiàn)方面的不同,而引發(fā)的bug,在AF中對(duì)于任務(wù)的開(kāi)始和暫停是需要進(jìn)行監(jiān)聽(tīng)的。

  • 簡(jiǎn)單地引用[NSURLSessionTask class]將不起作用。你需要讓一個(gè)NSURLSession來(lái)實(shí)際創(chuàng)建一個(gè)對(duì)象,并從那里獲取類。
  • 在iOS 7上,localDataTask是一個(gè)__NSCFLocalDataTask,它繼承自__NSCFLocalSessionTask,它繼承自__NSCFURLSessionTask
  • 在iOS 8上,localDataTask是一個(gè)__NSCFLocalDataTask,它繼承自__NSCFLocalSessionTask,它繼承自NSURLSessionTask
  • 在iOS 7上,__ NSCFLocalSessionTask__NSCFURLSessionTask是唯一具有自己的resumesuspend實(shí)現(xiàn)的兩個(gè)類,而__NSCFLocalSessionTask`則不會(huì)調(diào)用超級(jí)。這意味著兩個(gè)類都需要調(diào)整。
  • 在iOS 8上,NSURLSessionTask是唯一實(shí)現(xiàn)resumesuspend的類。這意味著這是唯一需要調(diào)整的類。
  • 因?yàn)槊總€(gè)iOS版本的類層次結(jié)構(gòu)都不涉及NSURLSessionTask,所以更容易將混合方法添加到虛擬類并在那里管理它們。

AF給出的解決方案

  • 沒(méi)有實(shí)現(xiàn)resumesuspend調(diào)用super。如果要在未來(lái)版本的iOS中進(jìn)行更改,我們需要處理它。
  • 沒(méi)有后臺(tái)任務(wù)類覆蓋resumesuspend
    1)通過(guò)向數(shù)據(jù)任務(wù)詢問(wèn)“NSURLSession”實(shí)例來(lái)獲取“__NSCFLocalDataTask”的實(shí)例。
    2)抓住指向af_resume的原始實(shí)現(xiàn)的指針
    3)檢查當(dāng)前類是否具有resume的實(shí)現(xiàn)。如果是,請(qǐng)繼續(xù)執(zhí)行步驟4。
    4) 獲取當(dāng)前類的父類
    5) 將當(dāng)前類的指針抓取到resume的當(dāng)前實(shí)現(xiàn)。
    6) 將超類的指針抓到resume的當(dāng)前實(shí)現(xiàn)中。
    7) 如果resume的當(dāng)前類實(shí)現(xiàn)不等于resume的超類實(shí)現(xiàn)并且resume的當(dāng)前實(shí)現(xiàn)不等于af_resume的原始實(shí)現(xiàn),那么swizzle方法
    8) 將當(dāng)前類設(shè)置為超類,并重復(fù)步驟3-8

以上是在AFURLSessionManager直譯的,可以通過(guò)源碼查看一下。
總結(jié)一下:
因?yàn)椴煌姹镜膶?shí)現(xiàn)不一致,可能會(huì)導(dǎo)致無(wú)法監(jiān)聽(tīng)到resumesuspend會(huì)出現(xiàn)問(wèn)題,那么通過(guò)運(yùn)行時(shí)將獲取方法的指針,交換方法。用_AFURLSessionTaskSwizzling類中實(shí)現(xiàn)的方法,替換系統(tǒng)的方法,規(guī)避版本實(shí)現(xiàn)不同而導(dǎo)致的問(wèn)題。

下面解析一下源碼(程序在啟動(dòng)后就會(huì)系統(tǒng)的resume、suspend就會(huì)被替換)

+ (void)load {
    /**
     WARNING: Trouble Ahead
     https://github.com/AFNetworking/AFNetworking/pull/2702
     */
    if (NSClassFromString(@"NSURLSessionTask")) {

        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
      
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
        // 創(chuàng)建一個(gè)對(duì)象
        NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
        
        IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
        
        // 通過(guò)對(duì)象獲取具體的類
        Class currentClass = [localDataTask class];
        
        // 3. 檢查當(dāng)前類是否具有resume的實(shí)現(xiàn)
        while (class_getInstanceMethod(currentClass, @selector(resume))) {
            
            // 4. 獲取當(dāng)前類的父類
            Class superClass = [currentClass superclass];
            
            // 指向函數(shù)的指針
            IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
           
            IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
           
            // 交換條件
            if (classResumeIMP != superclassResumeIMP &&
                originalAFResumeIMP != classResumeIMP) {
                
                [self swizzleResumeAndSuspendMethodForClass:currentClass];
            }
            
            // 當(dāng)前類指向父類
            currentClass = [currentClass superclass];
        }
        
        // 取消任務(wù)
        [localDataTask cancel];
        // 完成任務(wù)和檢測(cè)
        [session finishTasksAndInvalidate];
    }
}

- (void)af_resume { // 開(kāi)始會(huì)話
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    
    NSURLSessionTaskState state = [self state];
    
    // 調(diào)用系統(tǒng)的
    [self af_resume];
    
    // 發(fā)起通知
    if (state != NSURLSessionTaskStateRunning) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
    }
}

- (void)af_suspend { // 暫停會(huì)話
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
   
    NSURLSessionTaskState state = [self state];
   
    // 調(diào)用系統(tǒng)的
    [self af_suspend];
    
    // 發(fā)起通知
    if (state != NSURLSessionTaskStateSuspended) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
    }
}

當(dāng)外界一旦調(diào)用[dataTask resume];就會(huì)進(jìn)入該類自己實(shí)現(xiàn)的方法中,發(fā)起通知。這樣就可以監(jiān)聽(tīng)開(kāi)始和暫停了。

Snip20190220_7.png

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容