此文章主要記錄筆者在AFNetworking
源碼閱讀中的一下個人理解, 每次閱讀都會記錄一下, 如有錯誤, 請指正.
1. AFNetworking的使用
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = 60.0f;
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html",nil];
[manager GET:@"" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
NSLog(@"%@", downloadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@", error);
}];
我們在使用AFNetworking
發(fā)送GET
請求時如上訴代碼, 這里我們以一個GET
請求為例, 逐步分析代碼.
AFNetworking
中核心的發(fā)送網(wǎng)絡請求的代碼在AFURLSessionManager
中, AFHTTPSessionManager
是AFURLSessionManager
的子類, 主要對HTTP
請求做了一寫封裝.
2. AFURLSessionManager實例創(chuàng)建
2. GET請求方法
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
// 創(chuàng)建一個task
NSURLSessionDataTask *extractedExpr = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
NSURLSessionDataTask *dataTask = extractedExpr;
// 執(zhí)行task, 開始網(wǎng)絡請求
[dataTask resume];
return dataTask;
}
這個方法看起來很簡單, 根據(jù)傳入的參數(shù)使用dataTaskWithHTTPMethod
方法創(chuàng)建dataTask
任務, 并且調用[dataTask resume]
方法開啟任務, 將創(chuàng)建的dataTask
任務返回.
dataTask
任務可以進行cancel
, suspend
和resume
操作.
-
cancel
:取消當前的任務, 但是會標記一個被取消的任務, 任務取消以后會調用-URLSession:task:didCompleteWithError:
方法, 將錯誤信息{ NSURLErrorDomain, NSURLErrorCancelled }
傳遞出去. 處于suspended
狀態(tài)的任務也可以被取消. -
suspend
:暫停當前任務, 被暫停的任務可能還會繼續(xù)調用代理方法, 比如匯報接收數(shù)據(jù)的情況, 但是不會在發(fā)送數(shù)據(jù). 超時計時器會在任務被暫停時掛起. -
resume
:不僅可以啟動任務, 還可以喚醒狀態(tài)為suspend
的任務.
3. dataTaskWithHTTPMethod方法
我們來看一下dataTaskWithHTTPMethod
方法是怎么創(chuàng)建dataTask
任務的.
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
// 1.將傳入的參數(shù), 以及其他參數(shù)創(chuàng)建一個request, 其中設置了header請求頭, 將參數(shù)進行百分號編碼
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
// 2.將request和session做關聯(lián), 在init的時候創(chuàng)建了session的代理, 利用session創(chuàng)建datatask, 以datatask的id作為key AFN代理作為value存入mutableTaskDelegatesKeyedByTaskIdentifier字典中, 建立關系. 利用session的代理將信息轉發(fā)到AFN的代理中做統(tǒng)一處理.
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
可以看到這個方法內部分為了兩部分, 第一部分是創(chuàng)建request
請求, 第二部分是通過request
請求創(chuàng)建dataTask
任務.
request
請求是通過AFURLRequestSerialization
的requestWithMethod
方法構建的,
關于 AFURLRequestSerialization
我會有一篇專門的文章去介紹,
附上傳送門 AFNetworking之AFURLRequestSerialization深入學習.
這里我們主要講解AFURLSessionManager
是如何處理dataTask
任務, 將代理方法在AFURLSessionManagerTaskDelegate
中做統(tǒng)一處理的.
4. 構建NSURLSessionDataTask
有了request
后, 就可以調用AFURLSessionManager
的方法來構建NSURLSessionDataTask
4.1 dataTaskWithRequest:方法
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
NSLog(@"%@", [NSThread currentThread]);
// 創(chuàng)建NSURLSessionDataTask
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
// 這里的作用:
// manager管理dataTask和afn自定義的delegate
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
同樣這個方法也分為兩個部分, 第一部分創(chuàng)建dataTask
任務, 第二部分調用addDelegateForDataTask
方法管理dataTask
和AFN
自定義的delegate
.
將dataTask
傳入block
中用__block
修飾, 是指針傳遞, 這樣就可以為dataTask
賦值.
4.2 url_session_manager_create_task_safely函數(shù)
static void url_session_manager_create_task_safely(dispatch_block_t block) {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
// Fix of bug
// Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
// Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
// 串行隊列中同步執(zhí)行任務
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
使用url_session_manager_create_task_safely
函數(shù)創(chuàng)建dataTask
, 主要為了解決在iOS8以前的一個Bug
.
4.3 addDelegateForDataTask:方法
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
// 將AFURLSessionManagerTaskDelegate和manager建立關系
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
// delegate弱引用self
delegate.manager = self;
// 將回調賦值給delegate, 在delegate的代理方法中執(zhí)行
delegate.completionHandler = completionHandler;
// self.taskDescriptionForSessionTasks其實就是manager地址
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
// 這是這里的關鍵---
// 將datatask和delegate建立關系, 以task.taskIdentifier作為key, delegate作為value, 存儲到一個mutableTaskDelegatesKeyedByTaskIdentifier(manager的屬性)字典中
[self setDelegate:delegate forTask:dataTask];
// 設置上傳和下載進度塊
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
問題: 為什么要使用AFNURLSessionManagerTaskDelegate?
使用一個全局的字段保存AFNURLSessionManagerTaskDelegate和task, 在session的代理方法中如果需要處理數(shù)據(jù), 就通過task取出對應的AFNURLSessionManagerTaskDelegate, 調用AFNURLSessionManagerTaskDelegate代理的對應方法進行統(tǒng)一數(shù)據(jù)的處理.
4.4 各種回調
@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession;
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;
各種回調, 供外界賦值, 會在代理方法中調用響應的回調, 將信息傳遞出去.
5.代理方法
在AFHTTPSessionManager
進行初始化的時候, 調用它的父類AFURLSessionManager
的初始化方法.
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
在AFURLSessionManager
中創(chuàng)建session
時, 設置了代理為AFURLSessionManager
, 所以NSURLSession
的相關代理是在AFURLSessionManager
中實現(xiàn)的.
我們來看一下AFHTTPSessionManager
實現(xiàn)的代理方法.
5.1 NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
當前session
失效時, 代理調用此代理方法.
如果外界實現(xiàn)了sessionDidBecomeInvalid
的回調, 就會將當前session
和錯誤信息error
發(fā)送出去, 并且發(fā)送一個名字為AFURLSessionDidInvalidateNotification
通知出去, 用戶可以監(jiān)聽這個通知.
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
// 默認方式處理
/*
NSURLSessionAuthChallengeUseCredential 使用指定證書
NSURLSessionAuthChallengePerformDefaultHandling 默認方式
NSURLSessionAuthChallengeCancelAuthenticationChallenge 取消挑戰(zhàn)
*/
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
// sessionDidReceiveAuthenticationChallenge自定義的block, 外界可以通過set方法調用賦值, 自定義處理跳轉
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
// 這里服務器要求客戶端接收挑戰(zhàn)的方式是NSURLAuthenticationMethodServerTrust
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 根據(jù)跳轉的保護空間提供的信任 創(chuàng)建挑戰(zhàn)證書
// 檢查服務端是否可以信任(在authenticationMethod為NSURLAuthenticationMethodServerTrust的情況下)
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 創(chuàng)建挑戰(zhàn)證書
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
當web服務器收到客戶端的請求時, 有時候需要驗證客戶端用戶是不是正常用戶, 在決定是否返回真實數(shù)據(jù), 這種情況成為客戶端接收到挑戰(zhàn).
客戶端根據(jù)服務器返回的challenge, 生成所需要的disposition(枚舉類型, 應對挑戰(zhàn)的方式)和credential(應對挑戰(zhàn)生成的證書), 調用completionHandler將disposition和credential回應給服務器.
關于這部分的內容會在我的另一篇文章中做相關介紹, 傳送門AFNetworking之AFSecurityPolicy深入學習.
5.2 NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(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);
}
}
服務器重定向的時候會調用這個代理方法, 通過completionHandler
將重定向的request
傳遞給服務器.
如果用戶實現(xiàn)了taskWillPerformHTTPRedirection
方法, 就將taskWillPerformHTTPRedirection
方法返回的重定向請求發(fā)送給服務器.
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *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 = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
同上, 這里不再做介紹.
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
{
NSInputStream *inputStream = nil;
if (self.taskNeedNewBodyStream) {
inputStream = self.taskNeedNewBodyStream(session, task);
} else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
inputStream = [task.originalRequest.HTTPBodyStream copy];
}
if (completionHandler) {
completionHandler(inputStream);
}
}
需要重新發(fā)送一個含有bodyStream的request給服務器的時候會調用調用此代理方法.
該方法會在下邊兩種情況下調用:
- task是由uploadTaskWithStramedRequest創(chuàng)建的, 那么在提供初始化的request body stram是會調用
- 因為認證挑戰(zhàn)或者其他可恢復的服務器錯誤芬膝,而導致需要客戶端重新發(fā)送一個含有body stream的request望门,這時候會調用
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
int64_t totalUnitCount = totalBytesExpectedToSend;
// 如果totalUnitCount獲取失敗, 就是用http header的Content-Length
if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
if(contentLength) {
totalUnitCount = (int64_t) [contentLength longLongValue];
}
}
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// 轉發(fā)到自定義的delegate中
if (delegate) {
[delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];
}
// 如果實現(xiàn)了自定義的block, 將數(shù)據(jù)傳送出去, 例如做進度條的展示
if (self.taskDidSendBodyData) {
self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
}
}
周期性調用, 通知代理發(fā)送到服務器端數(shù)據(jù)的進度.
調用delegateForTask
方法獲取和task
相關聯(lián)的AFURLSessionManagerTaskDelegate
對象, 將任務轉發(fā)到AFURLSessionManagerTaskDelegate
對象方法中做處理, 這個我們會在第6部分講解.
這是AFN轉發(fā)的第一個方法
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
// 根據(jù)task的taskIdentifier找delegate
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
// 完成以后, 從字典mutableTaskDelegatesKeyedByTaskIdentifier移除當前task
[self removeDelegateForTask:task];
}
// 調用block
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
數(shù)據(jù)傳輸完成的就會調用此代理方法, 無論是成功還是失敗.
調用delegateForTask方法獲取和task相關聯(lián)的AFURLSessionManagerTaskDelegate對象, 將任務轉發(fā)到AFURLSessionManagerTaskDelegate對象方法中做處理
這是AFN轉發(fā)的第二個方法
5.3 NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
/*
NSURLSessionResponseCancel, dataTask會取消, 相當于調用了[task cancel]方法
NSURLSessionResponseAllow, dataTask正常運行
NSURLSessionResponseBecomeDownload, 變?yōu)閐ownloadTask, 會調用URLSession:dataTask:didBecomeDownloadTask:方法
NSURLSessionResponseBecomeStream
*/
// 設置為請求繼續(xù)進行
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
if (self.dataTaskDidReceiveResponse) {
disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
}
if (completionHandler) {
completionHandler(disposition);
}
}
接收到服務端的相應的時候調用此代理方法, 詢問是否要將當前的dataTask
任務變成其他類型的任務, 如果不實現(xiàn)這個方法, 默認是任務繼續(xù)執(zhí)行.
在completionHandler
回調用需要傳入NSURLSessionResponseDisposition
類型, 就是接下來要怎樣處理dataTask
.
NSURLSessionResponseDisposition
是一個枚舉類型:
- NSURLSessionResponseCancel, dataTask會取消, 相當于調用了[task cancel]方法
- NSURLSessionResponseAllow, dataTask正常運行
- NSURLSessionResponseBecomeDownload, 變?yōu)閐ownloadTask, 會調用URLSession:dataTask:didBecomeDownloadTask:方法
- NSURLSessionResponseBecomeStream, 變?yōu)橐粋€流任務.
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
if (delegate) {
// 將dataTask關聯(lián)的delegate刪除
[self removeDelegateForTask:dataTask];
// 為downloadTask關聯(lián)delegate
[self setDelegate:delegate forTask:downloadTask];
}
if (self.dataTaskDidBecomeDownloadTask) {
self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
}
}
當dataTask
變成下載任務的時候就會調用這個代理方法.
根據(jù)dataTask
獲取與其關聯(lián)的delegate
, 并將dataTask
從mutableTaskDelegatesKeyedByTaskIdentifier
字典中移除, 將downloadTask
和delegate
做關聯(lián).
- (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);
}
}
接收到服務器的數(shù)據(jù)時會周期性調用此代理方法.
data
返回是自上一個調用以來接收到的數(shù)據(jù), 所以我們要用一個容器
去存儲這些data
數(shù)據(jù).
AFN
將這個方法轉發(fā)到AFURLSessionManagerTaskDelegate
對象中去處理, 將接收到的data
數(shù)據(jù)拼接到mutableData
中, 供后續(xù)處理.
這是AFN轉發(fā)的第三個方法
- (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);
}
}
詢問是否緩存response
響應, 當接收到所有的數(shù)據(jù)以后會調用此代理方法.
如果沒有實現(xiàn)這個方法, 默認使用創(chuàng)建session時使用的configuration對象決定緩存策略.
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
if (self.didFinishEventsForBackgroundURLSession) {
dispatch_async(dispatch_get_main_queue(), ^{
self.didFinishEventsForBackgroundURLSession(session);
});
}
}
當一個后臺任務完成, 并且你的app在后臺狀態(tài), app會在后臺自動重新運行, 并且調用app的UIApplicationDelegate
對象的-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
方法. 客戶端應當存儲completionHandler
回調, 并且在- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
方法中調用這個completionHandler
回調.
5.4 NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
if (self.downloadTaskDidFinishDownloading) {
// 自定義的block, 返回的是你想存儲的文件地址路徑
NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
// 如果fileURl路徑存在, 說明用戶想把數(shù)據(jù)存儲起來
if (fileURL) {
delegate.downloadFileURL = fileURL;
NSError *error = nil;
// 從臨時路徑移動到我們定義的路徑, 將location位置的文件全部移動到自定義的路徑fileURL處
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]) {
// 如果移動文件失敗, 就發(fā)送通知出去
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
}
return;
}
}
// 這里代用自定義代理的方法, 在自定義方法中做了上訴同樣的操作, 是不是重復了???
if (delegate) {
[delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
}
}
當下載任務完成的時候回調用此代理方法.
由于這個location
地址是臨時的, 所以必須將其移動到應用程序的沙箱容器目錄的永久位置中, 才能讀取.
如果要打開閱讀這個文件, 應該在其他線程進行操作.
這是AFN轉發(fā)的第四個方法
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
if (delegate) {
[delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
}
if (self.downloadTaskDidWriteData) {
self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
}
周期性調用此代理方法, 通知下載進度.
bytesWritten
:從上次調用這個代理方法以后接收到的數(shù)據(jù)
totalBytesWritten
:接收到數(shù)據(jù)的總字節(jié)數(shù)
totalBytesExpectedToWrite
:期望接收到的數(shù)據(jù)字節(jié)總數(shù), 有header中的Content-Length提供, 如果沒有提供的話 默認是NSURLSessionTransferSizeUnknown
將數(shù)據(jù)轉發(fā)到AFURLSessionManagerTaskDelegate
代理中.
這是AFN轉發(fā)的第五個方法
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
if (delegate) {
[delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes];
}
if (self.downloadTaskDidResume) {
self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
}
}
當下載任務重新開始時, 會調用此代理方法.
如果斷點續(xù)傳的下載任務被取消或者失敗了, 你可以請求一個resumeData對象, 這個對象包含了足夠的信息去重新開始下載任務.
你可以調用downloadTaskWithResumeData:方法或者downloadTaskWithResumeData:completionHandler:, 將resumeData作為方法的參數(shù).
當你調用這兩個方法時, 你會得到一個新的下載任務, 如果你重新開這個下載任務, 那么就會調用URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:代理方法, 意味著下載重新開始了.
這是AFN轉發(fā)的第六個方法
至此, AFN中實現(xiàn)的代理方法已經(jīng)全部描述完畢.
6. AFURLSessionManagerTaskDelegate中的方法
AFURLSessionManagerTaskDelegate
中的方法是有AFURLSessionManager
中實現(xiàn)的代理方法轉發(fā)過來的. 在AFURLSessionManagerTaskDelegate
的這些方法中做統(tǒng)一的處理.
6.1 NSURLSessionTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
// 強引用, 防止被提前釋放, 因為AFURLSessionManager強引用delegate, delegate有一個弱引用屬性manager.
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
// 創(chuàng)建一個userInfo字典, 存儲信息, 這里創(chuàng)建的userInfo會通過通知發(fā)送出去.
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
// 這里也是值得我們學習地方, mutableData存儲的數(shù)據(jù)我們用一個局部變量保存, 并且清空mutableData, 在這個方法結束以后局部變量被清空, mutableData也指向nil.
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;
}
// 數(shù)據(jù)存儲的位置
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
// 如果有錯誤信息, 處理錯誤信息
if (error) {
// 同樣將錯誤信息存在userInfo中, 通知發(fā)送出去.
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
// 使用group處理, 當所有的task任務都完成, 才會發(fā)送通知(個人理解為, AFN中并沒有監(jiān)聽這個隊列組, 是為了讓使用者通過監(jiān)聽隊列組)
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);
}
dispatch_async(dispatch_get_main_queue(), ^{
// 將userInfo通過通知信息發(fā)出去
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
// 并發(fā)隊列異步線程解析數(shù)據(jù), 不阻塞線程
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
// 解析數(shù)據(jù), 在AFURLResponseSerialization中解析, AFJSONResponseSerializer將數(shù)據(jù)解析成 json 格式
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
// 注意: 如果存在downloadFileURL, 證明已經(jīng)將數(shù)據(jù)存儲到了磁盤上了, 所以此處的responseObject存儲的是data存放的位置, 將responseObject通過completionHandler傳出去.
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
// 如果responseObject不為空, 就存儲到userInfo字典中, 會通過通知發(fā)送出去
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
// 如果解析錯誤, 將錯誤信息, 存儲到userInfo字典中, 會通過通知發(fā)送出去
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
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];
});
});
});
}
}
task完成以后會調用此代理方法, 無論成功還是失敗都會調用.
url_session_manager_completion_group()
是一個創(chuàng)建隊列組單例的函數(shù).
解析data數(shù)據(jù)會調用AFJSONResponseSerializer
的responseObjectForResponse
方法將data
數(shù)據(jù)解析成responseObject
, 關于AFJSONResponseSerializer
我會在另外一篇文章講解.
部分代碼的功能已經(jīng)在代碼中注釋.
6.2 NSURLSessionDataDelegate
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
dataTask:(NSData *)data
{
self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;
// 將接受到的數(shù)據(jù)拼接到mutableData中, 在接受數(shù)據(jù)完成的方法中, 操作mutableData解析即可.
[self.mutableData appendData:data];
}
此方法在AFURLSessionManager的URLSession:dataTask:dataTask:方法中調用.
將data
數(shù)據(jù)拼接到mutableData
中, 并且根據(jù)dataTask
設置downloadProgress
的進度.
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.uploadProgress.completedUnitCount = task.countOfBytesSent;
}
此方法主要記錄上傳進度.
6.3 NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
self.downloadProgress.completedUnitCount = totalBytesWritten;
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
self.downloadProgress.totalUnitCount = expectedTotalBytes;
self.downloadProgress.completedUnitCount = fileOffset;
}
這兩個方法主要記錄下載的進度.
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
將下載文件移動到指定的下載位置.