此文章主要記錄筆者在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
傳送GET
請求時如上訴程式碼, 這裡我們以一個GET
請求為例, 逐步分析程式碼.
AFNetworking
中核心的傳送網路請求的程式碼在AFURLSessionManager
中, AFHTTPSessionManager
是AFURLSessionManager
的子類, 主要對HTTP
請求做了一寫封裝.
2. AFURLSessionManager例項建立
複製程式碼
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
{
// 建立一個task
NSURLSessionDataTask *extractedExpr = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
NSURLSessionDataTask *dataTask = extractedExpr;
// 執行task, 開始網路請求
[dataTask resume];
return dataTask;
}
複製程式碼
這個方法看起來很簡單, 根據傳入的引數使用dataTaskWithHTTPMethod
方法建立dataTask
任務, 並且呼叫[dataTask resume]
方法開啟任務, 將建立的dataTask
任務返回.
dataTask
任務可以進行cancel
, suspend
和resume
操作.
cancel
:取消當前的任務, 但是會標記一個被取消的任務, 任務取消以後會呼叫-URLSession:task:didCompleteWithError:
方法, 將錯誤資訊{ NSURLErrorDomain, NSURLErrorCancelled }
傳遞出去. 處於suspended
狀態的任務也可以被取消.suspend
:暫停當前任務, 被暫停的任務可能還會繼續呼叫代理方法, 比如彙報接收資料的情況, 但是不會在傳送資料. 超時計時器會在任務被暫停時掛起.resume
:不僅可以啟動任務, 還可以喚醒狀態為suspend
的任務.
3. dataTaskWithHTTPMethod方法
我們來看一下dataTaskWithHTTPMethod
方法是怎麼建立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.將傳入的引數, 以及其他引數建立一個request, 其中設定了header請求頭, 將引數進行百分號編碼
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做關聯, 在init的時候建立了session的代理, 利用session建立datatask, 以datatask的id作為key AFN代理作為value存入mutableTaskDelegatesKeyedByTaskIdentifier字典中, 建立關係. 利用session的代理將資訊轉發到AFN的代理中做統一處理.
__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;
}
複製程式碼
可以看到這個方法內部分為了兩部分, 第一部分是建立request
請求, 第二部分是通過request
請求建立dataTask
任務.
request
請求是通過AFURLRequestSerialization
的requestWithMethod
方法構建的,
關於 AFURLRequestSerialization
我會有一篇專門的文章去介紹,
附上傳送門 AFNetworking之AFURLRequestSerialization深入學習.
這裡我們主要講解AFURLSessionManager
是如何處理dataTask
任務, 將代理方法在AFURLSessionManagerTaskDelegate
中做統一處理的.
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]);
// 建立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;
}
複製程式碼
同樣這個方法也分為兩個部分, 第一部分建立dataTask
任務, 第二部分呼叫addDelegateForDataTask
方法管理dataTask
和AFN
自定義的delegate
.
將dataTask
傳入block
中用__block
修飾, 是指標傳遞, 這樣就可以為dataTask
賦值.
4.2 url_session_manager_create_task_safely函式
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
// 序列佇列中同步執行任務
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
複製程式碼
使用url_session_manager_create_task_safely
函式建立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的代理方法中執行
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;
}
複製程式碼
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
中建立session
時, 設定了代理為AFURLSessionManager
, 所以NSURLSession
的相關代理是在AFURLSessionManager
中實現的.
我們來看一下AFHTTPSessionManager
實現的代理方法.
5.1 NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
複製程式碼
當前session
失效時, 代理呼叫此代理方法.
如果外界實現了sessionDidBecomeInvalid
的回撥, 就會將當前session
和錯誤資訊error
傳送出去, 並且傳送一個名字為AFURLSessionDidInvalidateNotification
通知出去, 使用者可以監聽這個通知.
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
// 預設方式處理
/*
NSURLSessionAuthChallengeUseCredential 使用指定證照
NSURLSessionAuthChallengePerformDefaultHandling 預設方式
NSURLSessionAuthChallengeCancelAuthenticationChallenge 取消挑戰
*/
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
// sessionDidReceiveAuthenticationChallenge自定義的block, 外界可以通過set方法呼叫賦值, 自定義處理跳轉
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
// 這裡伺服器要求客戶端接收挑戰的方式是NSURLAuthenticationMethodServerTrust
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 根據跳轉的保護空間提供的信任 建立挑戰證照
// 檢查服務端是否可以信任(在authenticationMethod為NSURLAuthenticationMethodServerTrust的情況下)
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 建立挑戰證照
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伺服器收到客戶端的請求時, 有時候需要驗證客戶端使用者是不是正常使用者, 在決定是否返回真實資料, 這種情況成為客戶端接收到挑戰. 客戶端根據伺服器返回的challenge, 生成所需要的disposition(列舉型別, 應對挑戰的方式)和credential(應對挑戰生成的證照), 呼叫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
傳遞給伺服器.
如果使用者實現了taskWillPerformHTTPRedirection
方法, 就將taskWillPerformHTTPRedirection
方法返回的重定向請求傳送給伺服器.
- (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);
}
}
複製程式碼
需要重新傳送一個含有bodyStream的request給伺服器的時候會呼叫呼叫此代理方法. 該方法會在下邊兩種情況下呼叫:
- task是由uploadTaskWithStramedRequest建立的, 那麼在提供初始化的request body stram是會呼叫
- 因為認證挑戰或者其他可恢復的伺服器錯誤,而導致需要客戶端重新傳送一個含有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];
// 轉發到自定義的delegate中
if (delegate) {
[delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];
}
// 如果實現了自定義的block, 將資料傳送出去, 例如做進度條的展示
if (self.taskDidSendBodyData) {
self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
}
}
複製程式碼
週期性呼叫, 通知代理髮送到伺服器端資料的進度.
呼叫delegateForTask
方法獲取和task
相關聯的AFURLSessionManagerTaskDelegate
物件, 將任務轉發到AFURLSessionManagerTaskDelegate
物件方法中做處理, 這個我們會在第6部分講解.
這是AFN轉發的第一個方法
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
// 根據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);
}
}
複製程式碼
資料傳輸完成的就會呼叫此代理方法, 無論是成功還是失敗. 呼叫delegateForTask方法獲取和task相關聯的AFURLSessionManagerTaskDelegate物件, 將任務轉發到AFURLSessionManagerTaskDelegate物件方法中做處理 這是AFN轉發的第二個方法
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, 變為downloadTask, 會呼叫URLSession:dataTask:didBecomeDownloadTask:方法
NSURLSessionResponseBecomeStream
*/
// 設定為請求繼續進行
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
if (self.dataTaskDidReceiveResponse) {
disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
}
if (completionHandler) {
completionHandler(disposition);
}
}
複製程式碼
接收到服務端的相應的時候呼叫此代理方法, 詢問是否要將當前的dataTask
任務變成其他型別的任務, 如果不實現這個方法, 預設是任務繼續執行.
在completionHandler
回撥用需要傳入NSURLSessionResponseDisposition
型別, 就是接下來要怎樣處理dataTask
.
NSURLSessionResponseDisposition
是一個列舉型別:
- NSURLSessionResponseCancel, dataTask會取消, 相當於呼叫了[task cancel]方法
- NSURLSessionResponseAllow, dataTask正常執行
- NSURLSessionResponseBecomeDownload, 變為downloadTask, 會呼叫URLSession:dataTask:didBecomeDownloadTask:方法
- NSURLSessionResponseBecomeStream, 變為一個流任務.
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
if (delegate) {
// 將dataTask關聯的delegate刪除
[self removeDelegateForTask:dataTask];
// 為downloadTask關聯delegate
[self setDelegate:delegate forTask:downloadTask];
}
if (self.dataTaskDidBecomeDownloadTask) {
self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
}
}
複製程式碼
當dataTask
變成下載任務的時候就會呼叫這個代理方法.
根據dataTask
獲取與其關聯的delegate
, 並將dataTask
從mutableTaskDelegatesKeyedByTaskIdentifier
字典中移除, 將downloadTask
和delegate
做關聯.
- (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);
}
}
複製程式碼
接收到伺服器的資料時會週期性呼叫此代理方法.
data
返回是自上一個呼叫以來接收到的資料, 所以我們要用一個容器
去儲存這些data
資料.
AFN
將這個方法轉發到AFURLSessionManagerTaskDelegate
物件中去處理, 將接收到的data
資料拼接到mutableData
中, 供後續處理.
這是AFN轉發的第三個方法
- (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
響應, 當接收到所有的資料以後會呼叫此代理方法.
如果沒有實現這個方法, 預設使用建立session時使用的configuration物件決定快取策略.
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
if (self.didFinishEventsForBackgroundURLSession) {
dispatch_async(dispatch_get_main_queue(), ^{
self.didFinishEventsForBackgroundURLSession(session);
});
}
}
複製程式碼
當一個後臺任務完成, 並且你的app在後臺狀態, 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路徑存在, 說明使用者想把資料儲存起來
if (fileURL) {
delegate.downloadFileURL = fileURL;
NSError *error = nil;
// 從臨時路徑移動到我們定義的路徑, 將location位置的檔案全部移動到自定義的路徑fileURL處
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]) {
// 如果移動檔案失敗, 就傳送通知出去
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
}
return;
}
}
// 這裡代用自定義代理的方法, 在自定義方法中做了上訴同樣的操作, 是不是重複了???
if (delegate) {
[delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
}
}
複製程式碼
當下載任務完成的時候回撥用此代理方法.
由於這個location
地址是臨時的, 所以必須將其移動到應用程式的沙箱容器目錄的永久位置中, 才能讀取.
如果要開啟閱讀這個檔案, 應該在其他執行緒進行操作.
這是AFN轉發的第四個方法
- (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
:從上次呼叫這個代理方法以後接收到的資料
totalBytesWritten
:接收到資料的總位元組數
totalBytesExpectedToWrite
:期望接收到的資料位元組總數, 有header中的Content-Length提供, 如果沒有提供的話 預設是NSURLSessionTransferSizeUnknown
將資料轉發到AFURLSessionManagerTaskDelegate
代理中.
這是AFN轉發的第五個方法
- (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);
}
}
複製程式碼
當下載任務重新開始時, 會呼叫此代理方法. 如果斷點續傳的下載任務被取消或者失敗了, 你可以請求一個resumeData物件, 這個物件包含了足夠的資訊去重新開始下載任務. 你可以呼叫downloadTaskWithResumeData:方法或者downloadTaskWithResumeData:completionHandler:, 將resumeData作為方法的引數. 當你呼叫這兩個方法時, 你會得到一個新的下載任務, 如果你重新開這個下載任務, 那麼就會呼叫URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:代理方法, 意味著下載重新開始了. 這是AFN轉發的第六個方法
至此, AFN中實現的代理方法已經全部描述完畢.
6. AFURLSessionManagerTaskDelegate中的方法
AFURLSessionManagerTaskDelegate
中的方法是有AFURLSessionManager
中實現的代理方法轉發過來的. 在AFURLSessionManagerTaskDelegate
的這些方法中做統一的處理.
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;
// 建立一個userInfo字典, 儲存資訊, 這裡建立的userInfo會通過通知傳送出去.
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
// 這裡也是值得我們學習地方, mutableData儲存的資料我們用一個區域性變數儲存, 並且清空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;
}
// 資料儲存的位置
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
// 如果有錯誤資訊, 處理錯誤資訊
if (error) {
// 同樣將錯誤資訊存在userInfo中, 通知傳送出去.
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
// 使用group處理, 當所有的task任務都完成, 才會傳送通知
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通過通知資訊發出去
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
// 併發佇列非同步執行緒解析資料, 不阻塞執行緒
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
// 解析資料, 在AFURLResponseSerialization中解析, AFJSONResponseSerializer將資料解析成 json 格式
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
// 注意: 如果存在downloadFileURL, 證明已經將資料儲存到了磁碟上了, 所以此處的responseObject儲存的是data存放的位置, 將responseObject通過completionHandler傳出去.
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
// 如果responseObject不為空, 就儲存到userInfo字典中, 會通過通知傳送出去
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
// 如果解析錯誤, 將錯誤資訊, 儲存到userInfo字典中, 會通過通知傳送出去
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()
是一個建立佇列組單例的函式.
解析data資料會呼叫AFJSONResponseSerializer
的responseObjectForResponse
方法將data
資料解析成responseObject
, 關於AFJSONResponseSerializer
我會在另外一篇文章講解.
部分程式碼的功能已經在程式碼中註釋.
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;
// 將接受到的資料拼接到mutableData中, 在接受資料完成的方法中, 操作mutableData解析即可.
[self.mutableData appendData:data];
}
複製程式碼
此方法在AFURLSessionManager的URLSession:dataTask:dataTask:方法中呼叫.
將data
資料拼接到mutableData
中, 並且根據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];
}
}
}
}
複製程式碼
將下載檔案移動到指定的下載位置.