AFNetworking之AFURLSessionManager深入學習

OneAlon發表於2018-01-31

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

  • 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請求是通過AFURLRequestSerializationrequestWithMethod方法構建的, 關於 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方法管理dataTaskAFN自定義的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, 並將dataTaskmutableTaskDelegatesKeyedByTaskIdentifier字典中移除, 將downloadTaskdelegate做關聯.

- (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資料會呼叫AFJSONResponseSerializerresponseObjectForResponse方法將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];
            }
        }
    }
}
複製程式碼

將下載檔案移動到指定的下載位置.

相關文章