AFNetworking之AFURLSessionManager深入學習

此文章主要記錄筆者在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中, AFHTTPSessionManagerAFURLSessionManager的子類, 主要對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, suspendresume操作.

  • 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請求是通過AFURLRequestSerializationrequestWithMethod方法構建的,
關于 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方法管理dataTaskAFN自定義的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, 并將dataTaskmutableTaskDelegatesKeyedByTaskIdentifier字典中移除, 將downloadTaskdelegate做關聯(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ù)會調用AFJSONResponseSerializerresponseObjectForResponse方法將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];
            }
        }
    }
}

將下載文件移動到指定的下載位置.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锰霜,隨后出現(xiàn)的幾起案子筹误,更是在濱河造成了極大的恐慌,老刑警劉巖癣缅,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厨剪,死亡現(xiàn)場離奇詭異,居然都是意外死亡友存,警方通過查閱死者的電腦和手機祷膳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來爬立,“玉大人钾唬,你說我怎么就攤上這事。” “怎么了抡秆?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵奕巍,是天一觀的道長。 經(jīng)常有香客問我儒士,道長的止,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任着撩,我火速辦了婚禮诅福,結果婚禮上,老公的妹妹穿的比我還像新娘拖叙。我一直安慰自己氓润,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布薯鳍。 她就那樣靜靜地躺著咖气,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挖滤。 梳的紋絲不亂的頭發(fā)上崩溪,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音斩松,去河邊找鬼伶唯。 笑死,一個胖子當著我的面吹牛惧盹,可吹牛的內容都是我干的乳幸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼岭参,長吁一口氣:“原來是場噩夢啊……” “哼反惕!你這毒婦竟也來了?” 一聲冷哼從身側響起演侯,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎背亥,沒想到半個月后秒际,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡狡汉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年娄徊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盾戴。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡寄锐,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情橄仆,我是刑警寧澤剩膘,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站盆顾,受9級特大地震影響怠褐,放射性物質發(fā)生泄漏。R本人自食惡果不足惜您宪,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一奈懒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宪巨,春花似錦磷杏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至天吓,卻和暖如春贿肩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背龄寞。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工汰规, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人物邑。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓溜哮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親色解。 傳聞我的和親對象是個殘疾皇子茂嗓,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353