接著上一篇的內(nèi)容往下講谅阿,如果沒看過上一篇內(nèi)容可以點這:
之前我們講到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去私有化處理的粹断。
接下來我們來看轉(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)該代碼很容易讀巫湘,這個方法大概做了以下幾件事:
- 生成了一個存儲這個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)贱傀,回到了用戶的手中。
接下來我們來補充之前AFURLResponseSerialization
這一塊是如何解析數(shù)據(jù)的:
如圖所示伊脓,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ù)分別為NSURLErrorCannotDecodeContentData
、AFURLResponseSerializationErrorDomain
及汉,這兩個參數(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還有一些其他的類型解析闯冷,大家可以自行去閱讀砂心,代碼還是很容易讀的,在這里就不浪費篇幅去講了蛇耀。
在AFURLSessionManager中辩诞,有這么一個類:_AFURLSessionTaskSwizzling
。這個類大概的作用就是替換掉NSUrlSession
中的resume
和suspend
方法纺涤。正常處理原有邏輯的同時译暂,多發(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)了
resume
和suspend
方法。而且子類沒有調(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));
}
因為有小伙伴問到過,所以我們來分析分析大家可能會覺得疑惑的地方:
- 首先可以注意
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)容了句灌。
現(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代理的字典映射嘶卧,progress
的KVO
添加等等,到NSUrlSession
的resume
之前這些準(zhǔn)備工作凉袱,仍舊是在主線程中的芥吟。 - 然后我們調(diào)用
NSUrlSession
的resume
,接著就跑到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到底做了什么衷快?(終)