AFNetworking到底做了什么葵擎?(二)

接著上一篇的內(nèi)容往下講谅阿,如果沒看過上一篇內(nèi)容可以點這:

AFNetworking到底做了什么?

之前我們講到NSUrlSession代理這一塊:

代理8:
/*
 task完成之后的回調(diào),成功和失敗都會回調(diào)這里
 函數(shù)討論:
 注意這里的error不會報告服務(wù)期端的error酬滤,他表示的是客戶端這邊的eroor签餐,比如無法解析hostname或者連不上host主機。
 */
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{   
    //根據(jù)task去取我們一開始創(chuàng)建綁定的delegate
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // delegate may be nil when completing a task in the background
    if (delegate) {
        //把代理轉(zhuǎn)發(fā)給我們綁定的delegate
        [delegate URLSession:session task:task didCompleteWithError:error];
        //轉(zhuǎn)發(fā)完移除delegate
        [self removeDelegateForTask:task];
    }
   
    //自定義Block回調(diào)
    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }  
}

這個代理就是task完成了的回調(diào)盯串,方法內(nèi)做了下面這幾件事:

  • 在這里我們拿到了之前和這個task對應(yīng)綁定的AF的delegate:
 - (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
    [self.lock unlock];

    return delegate;
}
  • 去轉(zhuǎn)發(fā)了調(diào)用了AF代理的方法氯檐。這個等我們下面講完NSUrlSession的代理之后會詳細說。
  • 然后把這個AF的代理和task的綁定解除了体捏,并且移除了相關(guān)的progress和通知:
 - (void)removeDelegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);
    //移除跟AF代理相關(guān)的東西
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    [self.lock lock];
    [delegate cleanUpProgressForTask:task];
    [self removeNotificationObserverForTask:task];
    [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
    [self.lock unlock];
}
  • 調(diào)用了自定義的Blcok:self.taskDidComplete(session, task, error);
    代碼還是很簡單的冠摄,至于這個通知,我們等會再來補充吧几缭。
NSURLSessionDataDelegate:
代理9:
//收到服務(wù)器響應(yīng)后調(diào)用
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    //設(shè)置默認(rèn)為繼續(xù)進行
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;

    //自定義去設(shè)置
    if (self.dataTaskDidReceiveResponse) {
        disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
    }

    if (completionHandler) {
        completionHandler(disposition);
    }
}

官方文檔翻譯如下:

函數(shù)作用:
告訴代理河泳,該data task獲取到了服務(wù)器端傳回的最初始回復(fù)(response)。注意其中的completionHandler這個block年栓,通過傳入一個類型為NSURLSessionResponseDisposition的變量來決定該傳輸任務(wù)接下來該做什么:
NSURLSessionResponseAllow 該task正常進行
NSURLSessionResponseCancel 該task會被取消
NSURLSessionResponseBecomeDownload 會調(diào)用URLSession:dataTask:didBecomeDownloadTask:方法來新建一個download task以代替當(dāng)前的data task
NSURLSessionResponseBecomeStream 轉(zhuǎn)成一個StreamTask

函數(shù)討論:
該方法是可選的乔询,除非你必須支持“multipart/x-mixed-replace”類型的content-type。因為如果你的request中包含了這種類型的content-type韵洋,服務(wù)器會將數(shù)據(jù)分片傳回來竿刁,而且每次傳回來的數(shù)據(jù)會覆蓋之前的數(shù)據(jù)黄锤。每次返回新的數(shù)據(jù)時,session都會調(diào)用該函數(shù)食拜,你應(yīng)該在這個函數(shù)中合理地處理先前的數(shù)據(jù)鸵熟,否則會被新數(shù)據(jù)覆蓋。如果你沒有提供該方法的實現(xiàn)负甸,那么session將會繼續(xù)任務(wù)流强,也就是說會覆蓋之前的數(shù)據(jù)。

總結(jié)一下:

  • 當(dāng)你把添加content-type的類型為multipart/x-mixed-replace那么服務(wù)器的數(shù)據(jù)會分片的傳回來呻待。然后這個方法是每次接受到對應(yīng)片響應(yīng)的時候會調(diào)被調(diào)用打月。你可以去設(shè)置上述4種對這個task的處理。
  • 如果我們實現(xiàn)了自定義Block蚕捉,則調(diào)用一下奏篙,不然就用默認(rèn)的NSURLSessionResponseAllow方式。
代理10:
//上面的代理如果設(shè)置為NSURLSessionResponseBecomeDownload迫淹,則會調(diào)用這個方法
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
    //因為轉(zhuǎn)變了task秘通,所以要對task做一個重新綁定
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    if (delegate) {
        [self removeDelegateForTask:dataTask];
        [self setDelegate:delegate forTask:downloadTask];
    }
    //執(zhí)行自定義Block
    if (self.dataTaskDidBecomeDownloadTask) {
        self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
    }
}
  • 這個代理方法是被上面的代理方法觸發(fā)的壤玫,作用就是新建一個downloadTask父泳,替換掉當(dāng)前的dataTask匾竿。所以我們在這里做了AF自定義代理的重新綁定操作薪贫。
  • 調(diào)用自定義Block。

按照順序來抵栈,其實還有個AF沒有去實現(xiàn)的代理:

//AF沒實現(xiàn)的代理
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask;

這個也是之前的那個代理蕾管,設(shè)置為NSURLSessionResponseBecomeStream則會調(diào)用到這個代理里來线衫。會新生成一個NSURLSessionStreamTask來替換掉之前的dataTask诲锹。

代理11:
//當(dāng)我們獲取到數(shù)據(jù)就會調(diào)用稿静,會被反復(fù)調(diào)用,請求到的數(shù)據(jù)就在這被拼裝完整
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];
    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}
  • 這個方法和上面didCompleteWithError算是NSUrlSession的代理中最重要的兩個方法了辕狰。
  • 我們轉(zhuǎn)發(fā)了這個方法到AF的代理中去,所以數(shù)據(jù)的拼接都是在AF的代理中進行的控漠。這也是情理中的蔓倍,畢竟每個響應(yīng)數(shù)據(jù)都是對應(yīng)各個task,各個AF代理的盐捷。在AFURLSessionManager都只是做一些公共的處理偶翅。
代理12:
/*當(dāng)task接收到所有期望的數(shù)據(jù)后,session會調(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);
    }
}

官方文檔翻譯如下:

函數(shù)作用:
詢問data task或上傳任務(wù)(upload task)是否緩存response聚谁。

函數(shù)討論:
當(dāng)task接收到所有期望的數(shù)據(jù)后,session會調(diào)用此代理方法滞诺。如果你沒有實現(xiàn)該方法形导,那么就會使用創(chuàng)建session時使用的configuration對象決定緩存策略环疼。這個代理方法最初的目的是為了阻止緩存特定的URLs或者修改NSCacheURLResponse對象相關(guān)的userInfo字典。
該方法只會當(dāng)request決定緩存response時候調(diào)用朵耕。作為準(zhǔn)則炫隶,responses只會當(dāng)以下條件都成立的時候返回緩存:
該request是HTTP或HTTPS URL的請求(或者你自定義的網(wǎng)絡(luò)協(xié)議,并且確保該協(xié)議支持緩存)
確保request請求是成功的(返回的status code為200-299)
返回的response是來自服務(wù)器端的阎曹,而非緩存中本身就有的
提供的NSURLRequest對象的緩存策略要允許進行緩存
服務(wù)器返回的response中與緩存相關(guān)的header要允許緩存
該response的大小不能比提供的緩存空間大太多(比如你提供了一個磁盤緩存伪阶,那么response大小一定不能比磁盤緩存空間還要大5%)

  • 總結(jié)一下就是一個用來緩存response的方法,方法中調(diào)用了我們自定義的Block处嫌,自定義一個response用來緩存栅贴。
NSURLSessionDownloadDelegate
代理13:
//下載完成的時候調(diào)用

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    //這個是session的,也就是全局的熏迹,后面的個人代理也會做同樣的這件事
    if (self.downloadTaskDidFinishDownloading) {
        
        //調(diào)用自定義的block拿到文件存儲的地址
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            //從臨時的下載路徑移動至我們需要的路徑
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
            //如果移動出錯
            if (error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            }
            return;
        }
    }
    //轉(zhuǎn)發(fā)代理
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}
  • 這個方法和之前的兩個方法:
 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)taskdidCompleteWithError:(NSError *)error;
 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;

總共就這3個方法檐薯,被轉(zhuǎn)調(diào)到AF自定義delegate中。

  • 方法做了什么看注釋應(yīng)該很簡單癣缅,就不贅述了厨剪。
代理14:
//周期性地通知下載進度調(diào)用
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    if (self.downloadTaskDidWriteData) {
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}

簡單說一下這幾個參數(shù):
bytesWritten 表示自上次調(diào)用該方法后,接收到的數(shù)據(jù)字節(jié)數(shù)
totalBytesWritten表示目前已經(jīng)接收到的數(shù)據(jù)字節(jié)數(shù)
totalBytesExpectedToWrite 表示期望收到的文件總字節(jié)數(shù)友存,是由Content-Length header提供祷膳。如果沒有提供,默認(rèn)是NSURLSessionTransferSizeUnknown屡立。

代理15:
//當(dāng)下載被取消或者失敗后重新恢復(fù)下載時調(diào)用
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
    //交給自定義的Block去調(diào)用
    if (self.downloadTaskDidResume) {
        self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
    }
}

官方文檔翻譯:

函數(shù)作用:
告訴代理直晨,下載任務(wù)重新開始下載了。

函數(shù)討論:
如果一個正在下載任務(wù)被取消或者失敗了膨俐,你可以請求一個resumeData對象(比如在userInfo字典中通過NSURLSessionDownloadTaskResumeData這個鍵來獲取到resumeData)并使用它來提供足夠的信息以重新開始下載任務(wù)勇皇。
隨后,你可以使用resumeData作為downloadTaskWithResumeData:或downloadTaskWithResumeData:completionHandler:的參數(shù)焚刺。當(dāng)你調(diào)用這些方法時敛摘,你將開始一個新的下載任務(wù)。一旦你繼續(xù)下載任務(wù)乳愉,session會調(diào)用它的代理方法URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:其中的downloadTask參數(shù)表示的就是新的下載任務(wù)兄淫,這也意味著下載重新開始了。

總結(jié)一下:

  • 其實這個就是用來做斷點續(xù)傳的代理方法蔓姚。可以在下載失敗的時候捕虽,拿到我們失敗的拼接的部分resumeData,然后用去調(diào)用downloadTaskWithResumeData:就會調(diào)用到這個代理方法來了坡脐。
  • 其中注意:fileOffset這個參數(shù)泄私,如果文件緩存策略或者最后文件更新日期阻止重用已經(jīng)存在的文件內(nèi)容,那么該值為0。否則晌端,該值表示當(dāng)前已經(jīng)下載data的偏移量捅暴。
  • 方法中僅僅調(diào)用了downloadTaskDidResume自定義Block。

至此NSUrlSesssion的delegate講完了斩松。大概總結(jié)下:

  • 每個代理方法對應(yīng)一個我們自定義的Block,如果Block被賦值了伶唯,那么就調(diào)用它。
  • 在這些代理方法里惧盹,我們做的處理都是相對于這個sessionManager所有的request的乳幸。是公用的處理。
  • 轉(zhuǎn)發(fā)了3個代理方法到AF的deleagate中去了钧椰,AF中的deleagate是需要對應(yīng)每個task去私有化處理的粹断。
分割圖.png

接下來我們來看轉(zhuǎn)發(fā)到AF的deleagate,一共3個方法:

AF代理1:
//AF實現(xiàn)的代理嫡霞!被從urlsession那轉(zhuǎn)發(fā)到這

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
 
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    
    //1)強引用self.manager瓶埋,防止被提前釋放;因為self.manager聲明為weak,類似Block

    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    //用來存儲一些相關(guān)信息诊沪,來發(fā)送通知用的
    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    //存儲responseSerializer響應(yīng)解析對象
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    
    //注意這行代碼的用法养筒,感覺寫的很Nice...把請求到的數(shù)據(jù)data傳出去,然后就不要這個值了釋放內(nèi)存
    NSData *data = nil;
    if (self.mutableData) {
        data = [self.mutableData copy];
        //We no longer need the reference, so nil it out to gain back some memory.
        self.mutableData = nil;
    }

    //繼續(xù)給userinfo填數(shù)據(jù)
    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }
    //錯誤處理
    if (error) {
        
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
        
        //可以自己自定義完成組 和自定義完成queue,完成回調(diào)
        dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
            if (self.completionHandler) {
                self.completionHandler(task.response, responseObject, error);
            }
            //主線程中發(fā)送完成通知
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });
        });
    } else {
        //url_session_manager_processing_queue AF的并行隊列
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            
            //解析數(shù)據(jù)
            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
            
            //如果是下載文件端姚,那么responseObject為下載的路徑
            if (self.downloadFileURL) {
                responseObject = self.downloadFileURL;
            }

            //寫入userInfo
            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }
            
            //如果解析錯誤
            if (serializationError) {
                userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
            }
            //回調(diào)結(jié)果
            dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, serializationError);
                }

                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });
    }
#pragma clang diagnostic pop
}

這個方法是NSUrlSession任務(wù)完成的代理方法中晕粪,主動調(diào)用過來的。配合注釋渐裸,應(yīng)該代碼很容易讀巫湘,這個方法大概做了以下幾件事:

  1. 生成了一個存儲這個task相關(guān)信息的字典:userInfo,這個字典是用來作為發(fā)送任務(wù)完成的通知的參數(shù)昏鹃。
  • 判斷了參數(shù)error的值尚氛,來區(qū)分請求成功還是失敗。
  • 如果成功則在一個AF的并行queue中洞渤,去做數(shù)據(jù)解析等后續(xù)操作:
static dispatch_queue_t url_session_manager_processing_queue() {
    static dispatch_queue_t af_url_session_manager_processing_queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
    });

    return af_url_session_manager_processing_queue;
}

注意AF的優(yōu)化的點阅嘶,雖然代理回調(diào)是串行的(不明白可以見本文最后)。但是數(shù)據(jù)解析這種費時操作载迄,確是用并行線程來做的讯柔。

  • 然后根據(jù)我們一開始設(shè)置的responseSerializer來解析data。如果解析成功宪巨,調(diào)用成功的回調(diào),否則調(diào)用失敗的回調(diào)溜畅。
    我們重點來看看返回數(shù)據(jù)解析這行:
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

我們點進去看看:

  @protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

  - (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end

原來就是這么一個協(xié)議方法捏卓,各種類型的responseSerializer類,都是遵守這個協(xié)議方法,實現(xiàn)了一個把我們請求到的data轉(zhuǎn)換為我們需要的類型的數(shù)據(jù)的方法怠晴。至于各種類型的responseSerializer如何解析數(shù)據(jù)遥金,我們到代理講完再來補充。

  • 這邊還做了一個判斷蒜田,如果自定義了GCD完成組completionGroup和完成隊列的話completionQueue稿械,會在加入這個組和在隊列中回調(diào)Block。否則默認(rèn)的是AF的創(chuàng)建的組:
static dispatch_group_t url_session_manager_completion_group() {
    static dispatch_group_t af_url_session_manager_completion_group;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_completion_group = dispatch_group_create();
    });

    return af_url_session_manager_completion_group;
}

和主隊列回調(diào)冲粤。AF沒有用這個GCD組做任何處理美莫,只是提供這個接口,讓我們有需求的自行調(diào)用處理梯捕。如果有對多個任務(wù)完成度的監(jiān)聽厢呵,可以自行處理。
而隊列的話傀顾,如果你不需要回調(diào)主線程襟铭,可以自己設(shè)置一個回調(diào)隊列。

  • 回到主線程短曾,發(fā)送了任務(wù)完成的通知:
dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });

這個通知這回AF有用到了寒砖,在我們對UIKit的擴展中,用到了這個通知嫉拐。

AF代理2:
- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    //拼接數(shù)據(jù)
    [self.mutableData appendData:data];
}

同樣被NSUrlSession代理轉(zhuǎn)發(fā)到這里哩都,拼接了需要回調(diào)的數(shù)據(jù)。

AF代理3:
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSError *fileManagerError = nil;
    self.downloadFileURL = nil;

    //AF代理的自定義Block
    if (self.downloadTaskDidFinishDownloading) {
        //得到自定義下載路徑
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        
        if (self.downloadFileURL) {
            //把下載路徑移動到我們自定義的下載路徑
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];
            
            //錯誤發(fā)通知
            if (fileManagerError) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}

下載成功了被NSUrlSession代理轉(zhuǎn)發(fā)到這里椭岩,這里有個地方需要注意下:

  • 之前的NSUrlSession代理和這里都移動了文件到下載路徑茅逮,而NSUrlSession代理的下載路徑是所有request公用的下載路徑,一旦設(shè)置判哥,所有的request都會下載到之前那個路徑献雅。
  • 而這個是對應(yīng)的每個task的,每個task可以設(shè)置各自下載路徑,還記得AFHttpManager的download方法么
 [manager downloadTaskWithRequest:resquest progress:nil destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
    return path;
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
}];

這個地方return的path就是對應(yīng)的這個代理方法里的path塌计,我們調(diào)用最終會走到這么一個方法:

  - (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
                          progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                       destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                 completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    //返回地址的Block
    if (destination) {
        
        //有點繞挺身,就是把一個block賦值給我們代理的downloadTaskDidFinishDownloading,這個Block里的內(nèi)部返回也是調(diào)用Block去獲取到的锌仅,這里面的參數(shù)都是AF代理傳過去的章钾。
        delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
            //把Block返回的地址返回
            return destination(location, task.response);
        };
    }

    downloadTask.taskDescription = self.taskDescriptionForSessionTasks;

    [self setDelegate:delegate forTask:downloadTask];

    delegate.downloadProgressBlock = downloadProgressBlock;
}

清楚的可以看到地址被賦值給AF的Block了。

至此AF的代理也講完了热芹,數(shù)據(jù)或錯誤信息隨著AF代理成功失敗回調(diào)贱傀,回到了用戶的手中。

分割圖.png

接下來我們來補充之前AFURLResponseSerialization這一塊是如何解析數(shù)據(jù)的:

AFURLResponseSerialization.png

如圖所示伊脓,AF用來解析數(shù)據(jù)的一共上述這些方法府寒。第一個實際是一個協(xié)議方法,協(xié)議方法如下:

@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error;

@end

而后面6個類都是遵守這個協(xié)議方法,去做數(shù)據(jù)解析株搔。這地方可以再次感受一下AF的設(shè)計模式...接下來我們就來主要看看這些類對這個協(xié)議方法的實現(xiàn):

AFHTTPResponseSerializer:
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
    return data;
}
  • 方法調(diào)用了一個另外的方法之后剖淀,就把data返回來了,我們繼續(xù)往里看這個方法:
// 判斷是不是可接受類型和可接受code纤房,不是則填充error
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    //response是否合法標(biāo)識
    BOOL responseIsValid = YES;
    //驗證的error
    NSError *validationError = nil;

    //如果存在且是NSHTTPURLResponse
    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        
        //主要判斷自己能接受的數(shù)據(jù)類型和response的數(shù)據(jù)類型是否匹配纵隔,
        //如果有接受數(shù)據(jù)類型,如果不匹配response炮姨,而且響應(yīng)類型不為空捌刮,數(shù)據(jù)長度不為0
        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
            !([response MIMEType] == nil && [data length] == 0)) {
            
            //進入If塊說明解析數(shù)據(jù)肯定是失敗的,這時候要把解析錯誤信息放到error里剑令。
            //如果數(shù)據(jù)長度大于0糊啡,而且有響應(yīng)url
            if ([data length] > 0 && [response URL]) {
                
                //錯誤信息字典,填充一些錯誤信息
                NSMutableDictionary *mutableUserInfo = [@{
                                                          NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
                                                          NSURLErrorFailingURLErrorKey:[response URL],
                                                          AFNetworkingOperationFailingURLResponseErrorKey: response,
                                                        } mutableCopy];
                if (data) {
                    mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
                }

                //生成錯誤
                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
            }
            
            //返回標(biāo)識
            responseIsValid = NO;
        }

        //判斷自己可接受的狀態(tài)嗎
        //如果和response的狀態(tài)碼不匹配吁津,則進入if塊
        if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
            //填寫錯誤信息字典
            NSMutableDictionary *mutableUserInfo = [@{
                                               NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
                                               NSURLErrorFailingURLErrorKey:[response URL],
                                               AFNetworkingOperationFailingURLResponseErrorKey: response,
                                       } mutableCopy];

            if (data) {
                mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
            }

            //生成錯誤
            validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
            //返回標(biāo)識
            responseIsValid = NO;
        }
    }

    //給我們傳過來的錯誤指針賦值
    if (error && !responseIsValid) {
        *error = validationError;
    }

    //返回是否錯誤標(biāo)識
    return responseIsValid;
}
  • 看注釋應(yīng)該很容易明白這個方法有什么作用棚蓄。簡單來說,這個方法就是來判斷返回數(shù)據(jù)與咱們使用的解析器是否匹配碍脏,需要解析的狀態(tài)碼是否匹配梭依。如果錯誤,則填充錯誤信息典尾,并且返回NO役拴,否則返回YES,錯誤信息為nil钾埂。
  • 其中里面出現(xiàn)了兩個屬性值河闰,一個acceptableContentTypes,一個acceptableStatusCodes褥紫,兩者在初始化的時候有給默認(rèn)值姜性,我們也可以去自定義,但是如果給acceptableContentTypes定義了不匹配的類型髓考,那么數(shù)據(jù)仍舊會解析錯誤部念。
  • 而AFHTTPResponseSerializer僅僅是調(diào)用驗證方法,然后就返回了data氨菇。
AFJSONResponseSerializer:
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //先判斷是不是可接受類型和可接受code
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        //error為空儡炼,或者有錯誤,去函數(shù)里判斷查蓉。
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            //返回空
            return nil;
        }
    }

    id responseObject = nil;
    NSError *serializationError = nil;
    // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
    // See https://github.com/rails/rails/issues/1742
    
    //如果數(shù)據(jù)為空
    BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
    //不空則去json解析
    if (data.length > 0 && !isSpace) {
        responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
    } else {
        return nil;
    }

    //判斷是否需要移除Null值
    if (self.removesKeysWithNullValues && responseObject) {
        responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    }
    
    //拿著json解析的error去填充錯誤信息
    if (error) {
        *error = AFErrorWithUnderlyingError(serializationError, *error);
    }

    //返回解析結(jié)果
    return responseObject;
}

注釋寫的很清楚乌询,大概需要講一下的是以下幾個函數(shù):

//1
AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain))
//2
AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
//3
AFErrorWithUnderlyingError(serializationError, *error);

之前注釋已經(jīng)寫清楚了這些函數(shù)的作用,首先來看第1個:

//判斷是不是我們自己之前生成的錯誤信息豌研,是的話返回YES
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
    //判斷錯誤域名和傳過來的域名是否一致妹田,錯誤code是否一致
    if ([error.domain isEqualToString:domain] && error.code == code) {
        return YES;
        
    }
    //如果userInfo的NSUnderlyingErrorKey有值竣灌,則在判斷一次。
    else if (error.userInfo[NSUnderlyingErrorKey]) {
        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
    }

    return NO;
}

這里可以注意秆麸,我們這里傳過去的code和domain兩個參數(shù)分別為NSURLErrorCannotDecodeContentDataAFURLResponseSerializationErrorDomain及汉,這兩個參數(shù)是我們之前判斷response可接受類型和code時候自己去生成錯誤的時候填寫的沮趣。

第二個:

static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    //分?jǐn)?shù)組和字典
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        
        //生成一個數(shù)組,只需要JSONObject.count個坷随,感受到大神寫代碼的嚴(yán)謹(jǐn)態(tài)度了嗎...
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            //調(diào)用自己
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }
        //看我們解析類型是mutable還是非muatable,返回mutableArray或者array
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
        
    } else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            id value = (NSDictionary *)JSONObject[key];
            //value空則移除
            if (!value || [value isEqual:[NSNull null]]) {
                [mutableDictionary removeObjectForKey:key];
            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
                //如果數(shù)組還是去調(diào)用自己
                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
            }
        }
        
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    return JSONObject;
}

方法主要還是通過遞歸的形式實現(xiàn)房铭。比較簡單。

第三個:

static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
    if (!error) {
        return underlyingError;
    }

    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        return error;
    }
    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

方法主要是把json解析的錯誤温眉,賦值給我們需要返回給用戶的error上缸匪。比較簡單,小伙伴們自己看看就好类溢。

至此凌蔬,AFJSONResponseSerializer就講完了。
而我們ResponseSerialize還有一些其他的類型解析闯冷,大家可以自行去閱讀砂心,代碼還是很容易讀的,在這里就不浪費篇幅去講了蛇耀。


分割圖.png

在AFURLSessionManager中辩诞,有這么一個類:_AFURLSessionTaskSwizzling。這個類大概的作用就是替換掉NSUrlSession中的resumesuspend方法纺涤。正常處理原有邏輯的同時译暂,多發(fā)送一個通知,以下是我們需要替換的新方法:


//被替換掉的方法撩炊,只要有TASK開啟或者暫停外永,都會執(zhí)行
- (void)af_resume {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_resume];
    
    if (state != NSURLSessionTaskStateRunning) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
    }
}
- (void)af_suspend {
    
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_suspend];
    
    if (state != NSURLSessionTaskStateSuspended) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
    }
}

這塊知識是關(guān)于OC的Runtime:method swizzling的,如果有不清楚的地方衰抑,可以看看這里method swizzling--by冰霜或者自行查閱象迎。

+ (void)load {
 
    if (NSClassFromString(@"NSURLSessionTask")) {
        
        // 1) 首先構(gòu)建一個NSURLSession對象session,再通過session構(gòu)建出一個_NSCFLocalDataTask變量

        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
        NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
        // 2) 獲取到af_resume實現(xiàn)的指針
        IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
        Class currentClass = [localDataTask class];
        
        // 3) 檢查當(dāng)前class是否實現(xiàn)了resume呛踊。如果實現(xiàn)了砾淌,繼續(xù)第4步。
        while (class_getInstanceMethod(currentClass, @selector(resume))) {
            
            // 4) 獲取到當(dāng)前class的父類(superClass)
            Class superClass = [currentClass superclass];
            
            // 5) 獲取到當(dāng)前class對于resume實現(xiàn)的指針
            IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
            
            //  6) 獲取到父類對于resume實現(xiàn)的指針
            IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
 
               // 7) 如果當(dāng)前class對于resume的實現(xiàn)和父類不一樣(類似iOS7上的情況)谭网,并且當(dāng)前class的resume實現(xiàn)和af_resume不一樣汪厨,才進行method swizzling。
            if (classResumeIMP != superclassResumeIMP &&
                originalAFResumeIMP != classResumeIMP) {
                //執(zhí)行交換的函數(shù)
                [self swizzleResumeAndSuspendMethodForClass:currentClass];
            }
            // 8) 設(shè)置當(dāng)前操作的class為其父類class愉择,重復(fù)步驟3~8
            currentClass = [currentClass superclass];
        }
        
        [localDataTask cancel];
        [session finishTasksAndInvalidate];
    }
}

原方法中有大量的英文注釋劫乱,我把它翻譯過來如下:

iOS 7和iOS 8在NSURLSessionTask實現(xiàn)上有些許不同织中,這使得下面的代碼實現(xiàn)略顯trick
關(guān)于這個問題,大家做了很多Unit Test衷戈,足以證明這個方法是可行的
目前我們所知的:

  • NSURLSessionTasks是一組class的統(tǒng)稱狭吼,如果你僅僅使用提供的API來獲取NSURLSessionTask的class,并不一定返回的是你想要的那個(獲取NSURLSessionTask的class目的是為了獲取其resume方法)
  • 簡單地使用[NSURLSessionTask class]并不起作用殖妇。你需要新建一個NSURLSession刁笙,并根據(jù)創(chuàng)建的session再構(gòu)建出一個NSURLSessionTask對象才行。
  • iOS 7上谦趣,localDataTask(下面代碼構(gòu)造出的NSURLSessionDataTask類型的變量疲吸,為了獲取對應(yīng)Class)的類型是 __NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask前鹅,__NSCFLocalSessionTask繼承自__NSCFURLSessionTask摘悴。
  • iOS 8上,localDataTask的類型為__NSCFLocalDataTask舰绘,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask蹂喻,__NSCFLocalSessionTask繼承自NSURLSessionTask
  • iOS 7上,__NSCFLocalSessionTask和__NSCFURLSessionTask是僅有的兩個實現(xiàn)了resume和suspend方法的類捂寿,另外__NSCFLocalSessionTask中的resume和suspend并沒有調(diào)用其父類(即__NSCFURLSessionTask)方法叉橱,這也意味著兩個類的方法都需要進行method swizzling。
  • iOS 8上者蠕,NSURLSessionTask是唯一實現(xiàn)了resume和suspend方法的類窃祝。這也意味著其是唯一需要進行method swizzling的類
  • 因為NSURLSessionTask并不是在每個iOS版本中都存在,所以把這些放在此處(即load函數(shù)中)踱侣,比如給一個dummy class添加swizzled方法都會變得很方便粪小,管理起來也方便。

一些假設(shè)前提:

  • 目前iOS中resume和suspend的方法實現(xiàn)中并沒有調(diào)用對應(yīng)的父類方法抡句。如果日后iOS改變了這種做法探膊,我們還需要重新處理。
  • 沒有哪個后臺task會重寫resume和suspend函數(shù)

其余的一部分翻譯在注釋中待榔,對應(yīng)那一行代碼逞壁。大概總結(jié)下這個注釋:

  • 其實這是被社區(qū)大量討論的一個bug,之前AF因為這個替換方法锐锣,會導(dǎo)致偶發(fā)性的crash腌闯,如果不要這個swizzle則問題不會再出現(xiàn),但是這樣會導(dǎo)致AF中很多UIKit的擴展都不能正常使用雕憔。
  • 原來這是因為iOS7和iOS8的NSURLSessionTask的繼承鏈不同導(dǎo)致的姿骏,而且在iOS7繼承鏈中會有兩個類都實現(xiàn)了resumesuspend方法。而且子類沒有調(diào)用父類的方法斤彼,我們則需要對著兩個類都進行方法替換分瘦。而iOS8只需要對一個類進行替換蘸泻。
  • 對著注釋看,上述方法代碼不難理解嘲玫,用一個while循環(huán)悦施,一級一級去獲取父類,如果實現(xiàn)了resume方法去团,則進行替換歼争。

但是有幾個點大家可能會覺得疑惑的,我們先把這個方法調(diào)用的替換的函數(shù)一塊貼出來渗勘。

//其引用的交換的函數(shù):
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
    Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
    Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));

    if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
        af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
    }

    if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
        af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
    }
}
static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}
static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
    return class_addMethod(theClass, selector,  method_getImplementation(method),  method_getTypeEncoding(method));
}

因為有小伙伴問到過,所以我們來分析分析大家可能會覺得疑惑的地方:

  1. 首先可以注意class_getInstanceMethod這個方法俩莽,它會獲取到當(dāng)前類繼承鏈逐級往上旺坠,第一個實現(xiàn)的該方法。所以說它獲取到的方法不能確定是當(dāng)前類還是父類的扮超。而且這里也沒有用dispatch_once_t來保證一個方法只交換一次取刃,那萬一這是父類的方法,當(dāng)前類換一次出刷,父類又換一次璧疗,不是等于沒交換么?...請注意這行判斷:
// 7) 如果當(dāng)前class對于resume的實現(xiàn)和父類不一樣(類似iOS7上的情況)馁龟,并且當(dāng)前class的resume實現(xiàn)和af_resume不一樣崩侠,才進行method swizzling。
if (classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP) { 
          //執(zhí)行交換的函數(shù)
         [self swizzleResumeAndSuspendMethodForClass:currentClass]; 
}

這個條件就杜絕了這種情況的發(fā)生坷檩,只有當(dāng)前類實現(xiàn)了這個方法却音,才可能進入這個if塊。

2.那iOS7兩個類都交換了af_resume矢炼,那豈不是父類換到子類方法了?...只能說又是沒仔細看代碼的...注意AF是去向當(dāng)前類添加af_resume方法系瓢,然后去交換當(dāng)前類的af_resume。所以說根本不會出現(xiàn)這種情況...

AFUrlSessionManager 基本上就這么多內(nèi)容了句灌。

分割圖.png

現(xiàn)在我們回到一開始初始化的這行代碼上:

self.operationQueue.maxConcurrentOperationCount = 1;

1)首先我們要明確一個概念夷陋,這里的并發(fā)數(shù)僅僅是回調(diào)代理的線程并發(fā)數(shù)。而不是請求網(wǎng)絡(luò)的線程并發(fā)數(shù)胰锌。請求網(wǎng)絡(luò)是由NSUrlSession來做的骗绕,它內(nèi)部維護了一個線程池,用來做網(wǎng)絡(luò)請求资昧。它調(diào)度線程,基于底層的CFSocket去發(fā)送請求和接收數(shù)據(jù)爹谭。這些線程是并發(fā)的

2)明確了這個概念之后榛搔,我們來梳理一下AF3.x的整個流程和線程的關(guān)系:

  • 我們一開始初始化sessionManager的時候诺凡,一般都是在主線程东揣,(當(dāng)然不排除有些人喜歡在分線程初始化...)
  • 然后我們調(diào)用get或者post等去請求數(shù)據(jù),接著會進行request拼接腹泌,AF代理的字典映射嘶卧,progressKVO添加等等,到NSUrlSessionresume之前這些準(zhǔn)備工作凉袱,仍舊是在主線程中的芥吟。
  • 然后我們調(diào)用NSUrlSessionresume,接著就跑到NSUrlSession內(nèi)部去對網(wǎng)絡(luò)進行數(shù)據(jù)請求了,在它內(nèi)部是多線程并發(fā)的去請求數(shù)據(jù)的专甩。
  • 緊接著數(shù)據(jù)請求完成后钟鸵,回調(diào)回來在我們一開始生成的并發(fā)數(shù)為1的NSOperationQueue中,這個時候會是多線程串行的回調(diào)回來的涤躲。(注:不明白的朋友可以看看雷純峰大神這篇iOS 并發(fā)編程之 Operation Queues
  • 然后我們到返回數(shù)據(jù)解析那一塊棺耍,我們自己又創(chuàng)建了并發(fā)的多線程,去對這些數(shù)據(jù)進行了各種類型的解析种樱。
  • 最后我們?nèi)绻凶远x的completionQueue蒙袍,則在自定義的queue中回調(diào)回來,也就是分線程回調(diào)回來嫩挤,否則就是主隊列害幅,主線程中回調(diào)結(jié)束。

3)最后我們來解釋解釋為什么回調(diào)Queue要設(shè)置并發(fā)數(shù)為1:

  • 我認(rèn)為AF這么做有以下兩點原因:
    1)眾所周知岂昭,AF2.x所有的回調(diào)是在一條線程以现,這條線程是AF的常駐線程,而這一條線程正是AF調(diào)度request的思想精髓所在约啊,所以第一個目的就是為了和之前版本保持一致叼风。
    2)因為跟代理相關(guān)的一些操作AF都使用了NSLock。所以就算Queue的并發(fā)數(shù)設(shè)置為n棍苹,因為多線程回調(diào)无宿,鎖的等待,導(dǎo)致所提升的程序速度也并不明顯枢里。反而多task回調(diào)導(dǎo)致的多線程并發(fā)孽鸡,平白浪費了部分性能。
    而設(shè)置Queue的并發(fā)數(shù)為1栏豺,(注:這里雖然回調(diào)Queue的并發(fā)數(shù)為1彬碱,仍然會有不止一條線程,但是因為是串行回調(diào)奥洼,所以同一時間巷疼,只會有一條線程在操作AFUrlSessionManager的那些方法。)至少回調(diào)的事件灵奖,是不需要多線程并發(fā)的嚼沿。回調(diào)沒有了NSLock的等待時間估盘,所以對時間并沒有多大的影響。(注:但是還是會有多線程的操作的骡尽,因為設(shè)置剛開始調(diào)起請求的時候遣妥,是在主線程的,而回調(diào)則是串行分線程攀细。)

當(dāng)然這僅僅是我個人的看法箫踩,如果有不同意見的歡迎交流~

至此我們AF3.X業(yè)務(wù)層的邏輯,基本上結(jié)束了谭贪。小伙伴們境钟,看到這你明白了AF做了什么了嗎?可能很多朋友要扔雞蛋了...可能你還是沒覺得AF到底有什么用俭识,我用NSUrlSession不也一樣慨削,我干嘛要用AF,在這里鱼的,我暫時賣個關(guān)子,等我們下篇講完AFSecurityPolicy和部分UIKit的擴展痘煤,以及AF2.x的核心類源碼實現(xiàn)之后凑阶,我們再好好總結(jié)。

后續(xù)文章:

AFNetworking之于https認(rèn)證
AFNetworking之UIKit擴展與緩存實現(xiàn)
AFNetworking到底做了什么衷快?(終)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宙橱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蘸拔,更是在濱河造成了極大的恐慌师郑,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件调窍,死亡現(xiàn)場離奇詭異宝冕,居然都是意外死亡,警方通過查閱死者的電腦和手機邓萨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門地梨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缔恳,你說我怎么就攤上這事宝剖。” “怎么了歉甚?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵万细,是天一觀的道長。 經(jīng)常有香客問我纸泄,道長赖钞,這世上最難降的妖魔是什么腰素? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮仁烹,結(jié)果婚禮上耸弄,老公的妹妹穿的比我還像新娘。我一直安慰自己卓缰,他們只是感情好计呈,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著征唬,像睡著了一般捌显。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上总寒,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天扶歪,我揣著相機與錄音,去河邊找鬼摄闸。 笑死善镰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的年枕。 我是一名探鬼主播炫欺,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼熏兄!你這毒婦竟也來了品洛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤摩桶,失蹤者是張志新(化名)和其女友劉穎桥状,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硝清,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡辅斟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了芦拿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砾肺。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖防嗡,靈堂內(nèi)的尸體忽然破棺而出艘希,到底是詐尸還是另有隱情凛虽,我是刑警寧澤杀餐,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布泄鹏,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏番官。R本人自食惡果不足惜庐完,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望徘熔。 院中可真熱鬧门躯,春花似錦、人聲如沸酷师。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽山孔。三九已至懂讯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間台颠,已是汗流浹背褐望。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留串前,地道東北人瘫里。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像荡碾,于是被迫代替她去往敵國和親谨读。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

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