【原】AFNetworking源碼閱讀(三)

【原】AFNetworking源碼閱讀(三)

本文轉載請註明出處 —— polobymulberry-博客園html

1. 前言


上一篇的話,主要是講了如何經過構建一個request來生成一個data task。可是對於NSURLSession部分卻沒有說起。主要是精力有限,準備在這一部分把NSURLSession的知識好好梳理一遍。一切先從上一篇中的addDelegateForDataTask:函數提及,而後再介紹AFURLSessionManagerTaskDelegate,最後結合AFURLSessionManager中的NSURLSession梳理一遍(可能會將部份內容放到下一篇)。git

2. 由addDelegateForDataTask引起


注意addDelegateForDataTask:這個函數並非AFURLSessionManagerTaskDelegate的函數,而是AFURLSessionManager的一個函數。這也側面說明了AFURLSessionManagerTaskDelegate和NSURLSessionTask的關係是由AFURLSessionManager管理的github

該函數除了對於AFURLSessionManagerTaskDelegate類型的成員變量delegate設置以外,最關鍵的代碼就是web

[self setDelegate:delegate forTask:dataTask];

這個setDelegate:forTask:函數字面意思是將一個session task和一個AFURLSessionManagerTaskDelegate類型的delegate變量綁在一塊兒,而這個綁在一塊兒的工做是由咱們的AFURLSessionManager所作。至於綁定的過程,就是以該session task的taskIdentifier爲keydelegate爲value,賦值給mutableTaskDelegatesKeyedByTaskIdentifier這個NSMutableDictionary類型的變量。知道了這二者是關聯在一塊兒的話,立刻就會產生另外的問題 —— 爲何要關聯以及怎麼關聯在一塊兒?索性咱們好好研究下setDelegate:forTask:這個函數:緩存

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [delegate setupProgressForTask:task];
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

代碼首先是基本的判斷,判斷session task和delegate是否爲空,這裏實現方式(NSParameterAssert)每次看到都加深一下印象。接着就是使用NSLock來加鎖,這個很簡單,和@synchronized做用相似,不過@synchronized多了一個可使用變量做爲互斥信號量的功能,這裏就不細說了。臨界區的代碼(lock和unlock之間的代碼)也是分爲三個部分:安全

1. self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
2. [delegate setupProgressForTask:task];
3. [self addNotificationObserverForTask:task];
  • 第一部分不贅述了。
  • 第二部分我掃了下代碼,好像是設置兩個NSProgress的變量 - uploadProgress和downloadProgress。
  • 第三部分就是給session task添加了兩個KVO事件。

具體細節詳述以下(包含第二部分和第三部分詳述):服務器

2.1 –[AFURLSessionManager setupProgressForTask:]

上面簡單提了下該函數是爲了設置uploadProgress和downloadProgress這兩個NSProgress變量。這種設置也是很合理的,畢竟session task的任務中須要記錄進度的,要不是上傳任務,要不就是下載任務。網絡

咱們來看看setupProgressForTask:的具體實現:session

- (void)setupProgressForTask:(NSURLSessionTask *)task {
    __weak __typeof__(task) weakTask = task;

    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
    [self.uploadProgress setCancellable:YES];
    [self.uploadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.uploadProgress setPausable:YES];
    [self.uploadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];
    if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.uploadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    [self.downloadProgress setCancellable:YES];
    [self.downloadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.downloadProgress setPausable:YES];
    [self.downloadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];

    if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.downloadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
              options:NSKeyValueObservingOptionNew
              context:NULL];

    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
              options:NSKeyValueObservingOptionNew
              context:NULL];

    [self.downloadProgress addObserver:self
                            forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                               options:NSKeyValueObservingOptionNew
                               context:NULL];
    [self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
}

先是設置兩個progress的totalUnitCount:app

self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;

上傳的totalUnitCount就對應指望發送(send)的數據大小,下載任務的就對應指望接收(receive)的數據大小。

接着就是設置這兩個NSProgress對應的cancelpauseresume這三個狀態,正好對應session task的cancel、suspend和resume三個狀態,詳見上方源碼。

最後一部分代碼是關鍵,給session task和兩個progress添加KVO。也就是說該AFURLSessionManager的對象須要觀察如下屬性:

  • NSURLSessionTask的countOfBytesReceived、countOfBytesExpectedToReceive、countOfBytesSent、countOfBytesExpectedToSend屬性
  • NSProgress的fractionCompleted屬性(任務已經完成的比例,取值爲0~1)

看了KVO,立刻跳到observeValueForKeyPath:ofObject:change:context:函數中:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([object isKindOfClass:[NSURLSessionTask class]]) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
            self.downloadProgress.completedUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
            self.downloadProgress.totalUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
            self.uploadProgress.completedUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
            self.uploadProgress.totalUnitCount = [change[@"new"] longLongValue];
        }
    }
    else if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}

總結一下,也就是說

  • downloadProgress.completedUnitCount 《==      countOfBytesReceived更新
  • downloadProgress.totalUnitCount          《==      countOfBytesExpectedToReceive更新
  • uploadProgress.completedUnitCount      《==      countOfBytesSent更新
  • uploadProgress.totalUnitCount               《==      countOfBytesExpectedToSend更新
  • 調用自定義的downloadProgressBlock        《==      downloadProgress.fractionCompleted更新
  • 調用自定義的uploadProgressBlock             《==      uploadProgress.fractionCompleted更新

最後兩個KVO事件中使用的block其實就是根據NSProgress的狀態作用戶自定義的行爲,好比須要更新UI進度條的狀態之類的。

2.2 –[AFURLSessionManager addNotificationObserverForTask:]

- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}

此處若是往深的地方研究,會涉及到不少runtime甚至methodSwizzle的代碼,後面會專門開一個章節,研究下這段代碼。此處咱們只需知道,當NSURLSessionTask調用resume函數時,會postNotificationName:AFNSURLSessionTaskDidResumeNotification,從而執行taskDidResume:方法:

- (void)taskDidResume:(NSNotification *)notification {
    NSURLSessionTask *task = notification.object;
    if ([task respondsToSelector:@selector(taskDescription)]) {
        if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
            });
        }
    }
}

有了AFNetworkingTaskDidResumeNotification就方便了,以前咱們UIRefreshControl+AFNEtworking中就使用AFNetworkingTaskDidResumeNotification做爲NotificationName。同理,參考源碼對AFNSURLSessionTaskDidSuspendNotification的處理,這裏就不贅述了。

3. 詳解AFURLSessionManager的NSURLSession的相關代理


咱們以前在看GET:等這些上層函數時,發現內部實現就是爲了生成一個session task。而這個session task與網絡具體如何交互,如何處理數據的方法,則是寫在NSURLSession的相關代理方法中。雖然GET:這些方法是AFHTTPSessionManager的方法,可是AFURLSessionManager是AFHTTPSessionManager的父類,因此調用的NSURLSession的相關代理的實現實際上是在AFURLSessionManager中實現的,咱們能夠看看AFURLSessionManager實現了哪些NSURLSession相關的代理方法:

 ### `NSURLSessionDelegate`

 - `URLSession:didBecomeInvalidWithError:`
 - `URLSession:didReceiveChallenge:completionHandler:`
 - `URLSessionDidFinishEventsForBackgroundURLSession:`

 ### `NSURLSessionTaskDelegate`

 - `URLSession:willPerformHTTPRedirection:newRequest:completionHandler:`
 - `URLSession:task:didReceiveChallenge:completionHandler:`
 - `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`
 - `URLSession:task:didCompleteWithError:`

 ### `NSURLSessionDataDelegate`

 - `URLSession:dataTask:didReceiveResponse:completionHandler:`
 - `URLSession:dataTask:didBecomeDownloadTask:`
 - `URLSession:dataTask:didReceiveData:`
 - `URLSession:dataTask:willCacheResponse:completionHandler:`

 ### `NSURLSessionDownloadDelegate`

 - `URLSession:downloadTask:didFinishDownloadingToURL:`
 - `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`
 - `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`

3.1 NSURLSessionDelegate

3.1.1 - URLSession:didBecomeInvalidWithError:

函數聲明:

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error

函數做用:

當前這個session已經失效時,該代理方法被調用。

函數討論:

若是你使用finishTasksAndInvalidate函數使該session失效,那麼session首先會先完成最後一個task,而後再調用URLSession:didBecomeInvalidWithError:代理方法,若是你調用invalidateAndCancel方法來使session失效,那麼該session會當即調用上面的代理方法。

函數實現:

- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
    // 自定義的一個block,用來處理session無效的狀況。
    // 此處插一句,剛纔忽然靈光一現,體驗到了block的好處。具體說不清楚,
    // 我只能說好處就是此處並非讓用戶本身實現didBecomeInvalidWithError:方法,
    // 而是讓用戶實現sessionDidBecomeInvalid這個block,隱藏細節。
    // 確實很妙,之後要學會使用block
    if (self.sessionDidBecomeInvalid) {
        self.sessionDidBecomeInvalid(session, error);
    }
    
    // 當一個session無效時,post名爲AFURLSessionDidInvalidateNotification的Notification
    // 不過源代碼中沒有舉例如何使用這個Notification,因此須要用戶本身定義,好比結束進度條的顯示啊。
    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

3.1.2 - URLSession:didReceiveChallenge:completionHandler:

函數聲明:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler;

函數做用:

web服務器接收到客戶端請求時,有時候須要先驗證客戶端是否爲正經常使用戶,再決定是夠返回真實數據。這種狀況稱之爲服務端要求客戶端接收挑戰(NSURLAuthenticationChallenge *challenge)。接收到挑戰後,客戶端要根據服務端傳來的challenge來生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(disposition指定應對這個挑戰的方法,而credential是客戶端生成的挑戰證書,注意只有challenge中認證方法爲NSURLAuthenticationMethodServerTrust的時候,才須要生成挑戰證書)。最後調用completionHandler迴應服務器端的挑戰。

函數討論:

該代理方法會在下面兩種狀況調用:

  1. 1. 當服務器端要求客戶端提供證書時或者進行NTLM認證(Windows NT LAN Manager,微軟提出的WindowsNT挑戰/響應驗證機制)時,此方法容許你的app提供正確的挑戰證書。
  2. 2. 當某個session使用SSL/TLS協議,第一次和服務器端創建鏈接的時候,服務器會發送給iOS客戶端一個證書,此方法容許你的app驗證服務期端的證書鏈(certificate keychain)

若是你沒有實現該方法,該session會調用其NSURLSessionTaskDelegate的代理方法URLSession:task:didReceiveChallenge:completionHandler: 。

函數實現:

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    //挑戰處理類型爲 默認
    /*
    NSURLSessionAuthChallengePerformDefaultHandling:默認方式處理
    NSURLSessionAuthChallengeUseCredential:使用指定的證書
    NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰
    */
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    // sessionDidReceiveAuthenticationChallenge是自定義方法,用來如何應對服務器端的認證挑戰
    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        // 此處服務器要求客戶端的接收認證挑戰方法是NSURLAuthenticationMethodServerTrust
        // 也就是說服務器端須要客戶端返回一個根據認證挑戰的保護空間提供的信任(即challenge.protectionSpace.serverTrust)產生的挑戰證書。
        // 而這個證書就須要使用credentialForTrust:來建立一個NSURLCredential對象
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            // 基於客戶端的安全策略來決定是否信任該服務器,不信任的話,也就不必響應挑戰
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                // 建立挑戰證書(注:挑戰方式爲UseCredential和PerformDefaultHandling都須要新建挑戰證書)
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                // 肯定挑戰的方式 
                 if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                // 取消挑戰
                disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }
    
    // 必須調用此方法,完成認證挑戰
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

3.1.3 – URLSessionDidFinishEventsForBackgroundURLSession:

函數聲明:

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session

函數做用:

當session中全部已經入隊的消息被髮送出去後,會調用該代理方法。

函數討論:

在iOS中,當一個後臺傳輸任務完成或者後臺傳輸時須要證書,而此時你的app正在後臺掛起,那麼你的app在後臺會自動從新啓動運行,而且這個app的UIApplicationDelegate會發送一個application:handleEventsForBackgroundURLSession:completionHandler:消息。該消息包含了對應後臺的session的identifier,並且這個消息會致使你的app啓動。你的app隨後應該先存儲completion handler,而後再使用相同的identifier建立一個background configuration,並根據這個background configuration建立一個新的session。這個新建立的session會自動與後臺任務從新關聯在一塊兒。

當你的app獲取了一個URLSessionDidFinishEventsForBackgroundURLSession:消息,這就意味着以前這個session中已經入隊的全部消息都轉發出去了,這時候再調用先前存取的completion handler是安全的,或者由於內部更新而致使調用completion handler也是安全的。

函數實現:

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    if (self.didFinishEventsForBackgroundURLSession) {
        // 意味着background session中的消息已經所有發送出去了,返回到主進程執行自定義的函數
        dispatch_async(dispatch_get_main_queue(), ^{
            self.didFinishEventsForBackgroundURLSession(session);
        });
    }
}

3.2 NSURLSessionTaskDelegate

3.2.1 - URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:

函數聲明:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler

函數做用:

客戶端告知服務器端須要HTTP重定向。

函數討論:
此方法只會在default session或者ephemeral session中調用,而在background session中,session task會自動重定向。


知識點

對於NSURLSession對象的初始化須要使用NSURLSessionConfiguration,而NSURLSessionConfiguration有三個類工廠方法:

+defaultSessionConfiguration 返回一個標準的 configuration,這個配置實際上與 NSURLConnection網絡堆棧(networking stack)是同樣的,具備相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享NSURLCredentialStorage

+ephemeralSessionConfiguration 返回一個預設配置,這個配置中不會對緩存,Cookie 和證書進行持久性的存儲。這對於實現像祕密瀏覽這種功能來講是很理想的。

+backgroundSessionConfiguration:(NSString *)identifier 的獨特之處在於,它會建立一個後臺 session。後臺 session 不一樣於常規的,普通的 session,它甚至能夠在應用程序掛起,退出或者崩潰的狀況下運行上傳和下載任務。初始化時指定的標識符,被用於向任何可能在進程外恢復後臺傳輸的守護進程(daemon)提供上下文。


函數實現:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest *))completionHandler
{
    NSURLRequest *redirectRequest = request;
    // 自定義如何處理重定向請求,注意會生成一個新的request
    if (self.taskWillPerformHTTPRedirection) {
        redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
    }

    if (completionHandler) {
        completionHandler(redirectRequest);
    }
}

3.2.2 - URLSession:task:didReceiveChallenge:completionHandler:

函數聲明:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

函數做用:

同NSURLSessionDelegate中的- URLSession:didReceiveChallenge:completionHandler:

函數討論:

該方法是處理task-level的認證挑戰。在NSURLSessionDelegate中提供了一個session-level的認證挑戰代理方法。該方法的調用取決於認證挑戰的類型:

  • 對於session-level的認證挑戰,挑戰類型有 — NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate, NSURLAuthenticationMethodClientCertificate, 或NSURLAuthenticationMethodServerTrust — 此時session會調用其代理方法URLSession:didReceiveChallenge:completionHandler:。若是你的app沒有提供對應的NSURLSessionDelegate方法,那麼NSURLSession對象就會調用URLSession:task:didReceiveChallenge:completionHandler:來處理認證挑戰。
  • 對於non-session-level的認證挑戰,NSURLSession對象調用URLSession:task:didReceiveChallenge:completionHandler:來處理認證挑戰。若是你在app中使用了session代理方法,並且也確實要處理認證挑戰這個問題,那麼你必須仍是在task level來處理這個問題,或者提供一個task-level的handler來顯式調用每一個session的handler。而對於non-session-level的認證挑戰,session的delegate中的URLSession:didReceiveChallenge:completionHandler:方法不會被調用。

函數實現:

參考URLSession:didReceiveChallenge:completionHandler:實現,除了自定義了taskDidReceiveAuthenticationChallenge這個block處理task-level的認證挑戰,其餘都同樣。

3.2.3 - URLSession:task:needNewBodyStream:

函數聲明:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler

函數做用:

當一個session task須要發送一個新的request body stream到服務器端的時候,調用該代理方法。

函數討論:

該代理方法會在下面兩種狀況被調用:

  • 若是task是由uploadTaskWithStreamedRequest:建立的,那麼提供初始的request body stream時候會調用該代理方法。
  • 由於認證挑戰或者其餘可恢復的服務器錯誤,而致使須要客戶端從新發送一個含有body stream的request,這時候會調用該代理。

函數實現:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
 needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
{
    NSInputStream *inputStream = nil;

    if (self.taskNeedNewBodyStream) {
        // 自定義的獲取到新的bodyStream方法
        inputStream = self.taskNeedNewBodyStream(session, task);
    } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
        // 拷貝一份數據出來到新的bodyStream中(即inputStream)
        inputStream = [task.originalRequest.HTTPBodyStream copy];
    }

    if (completionHandler) {
        completionHandler(inputStream);
    }
}

3.2.4 - URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:

函數聲明:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend

函數做用:

週期性地通知代理髮送到服務器端數據的進度。

函數實現:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
    // 若是totalUnitCount獲取失敗,就使用HTTP header中的Content-Length做爲totalUnitCount
    int64_t totalUnitCount = totalBytesExpectedToSend;
    if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
        NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
        if(contentLength) {
            totalUnitCount = (int64_t) [contentLength longLongValue];
        }
    }
    // 每次發送數據後的相關自定義處理,好比根據totalBytesSent來進行UI界面的數據上傳顯示
    if (self.taskDidSendBodyData) {
        self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
    }
}

3.2.5 - URLSession:task:didCompleteWithError:

函數聲明:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error

函數做用:

告知該session task已經完成了數據傳輸任務。

函數討論:

注意這裏的error不會報告服務期端的error,他表示的是客戶端這邊的eroor,好比沒法解析hostname或者連不上host主機。

函數實現:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    // 這裏第一次展現了AFURLSessionManagerTaskDelegate的做用
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // 若是task是在後臺完成的,可能delegate會爲nil
    if (delegate) {
        // 調用了同樣的代理方法,不過是AFURLSessionManagerTaskDelegate中實現的
        [delegate URLSession:session task:task didCompleteWithError:error];
        // 該task結束了,就移除對應的delegate
        [self removeDelegateForTask:task];
    }
    // 自定義處理方法
    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

3.3 NSURLSessionDataDelegate

3.3.1 - URLSession:dataTask:didReceiveResponse:completionHandler:

函數聲明:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler

函數做用:

告訴代理,該data task獲取到了服務器端傳回的最初始回覆(response)。注意其中的completionHandler這個block,經過傳入一個類型爲NSURLSessionResponseDisposition的變量來決定該傳輸任務接下來該作什麼:

  • NSURLSessionResponseAllow 該task正常進行
  • NSURLSessionResponseCancel 該task會被取消
  • NSURLSessionResponseBecomeDownload 會調用URLSession:dataTask:didBecomeDownloadTask:方法來新建一個download task以代替當前的data task

函數討論:

該方法是可選的,除非你必須支持「multipart/x-mixed-replace」類型的content-type。由於若是你的request中包含了這種類型的content-type,服務器會將數據分片傳回來,並且每次傳回來的數據會覆蓋以前的數據。每次返回新的數據時,session都會調用該函數,你應該在這個函數中合理地處理先前的數據,不然會被新數據覆蓋。若是你沒有提供該方法的實現,那麼session將會繼續任務,也就是說會覆蓋以前的數據。

函數實現:

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{  
    // 默認方式爲繼續執行該task
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
    // 自定義
    if (self.dataTaskDidReceiveResponse) {
        disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
    }

    if (completionHandler) {
        completionHandler(disposition);
    }
}

3.3.2 - URLSession:dataTask:didBecomeDownloadTask:

函數聲明:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask

函數做用:

若是data task變化成了下載任務(download task),那麼就會調用該代理方法

函數討論:

好比在- URLSession:dataTask:didReceiveResponse:completionHandler:給completionHandler方法傳遞NSURLSessionResponseBecomeDownload,就會使data task變成download task。並且以前的data task不會再響應代理方法了。

函數實現:

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    if (delegate) {
        // 將delegate關聯的data task移除,換成新產生的download task
        [self removeDelegateForTask:dataTask];
        [self setDelegate:delegate forTask:downloadTask];
    }
    // 自定義
    if (self.dataTaskDidBecomeDownloadTask) {
        self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
    }
}

3.3.3 - URLSession:dataTask:didReceiveData:

函數聲明:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data

函數做用:

當接收到部分指望獲得的數據(expected data)時,會調用該代理方法。

函數討論:

一個NSData類型的數據一般是由一系列不一樣的數據整合到一塊兒獲得的,無論是否是這樣,請使用- enumerateByteRangesUsingBlock:來遍歷數據然不是使用bytes方法(由於bytes缺乏enumerateByteRangesUsingBlock方法中的range,有了range,enumerateByteRangesUsingBlock就能夠對NSData不一樣的數據塊進行遍歷,而不像bytes把全部NSData當作一個數據塊)。

該代理方法可能會調用屢次(好比分片獲取數據),你須要本身實現函數將全部數據整合在一塊兒。

函數實現:

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    // 調用的是AFURLSessionManagerTaskDelegate的didReceiveData方法
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];
    // 自定義
    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}

3.3.4 - URLSession:dataTask:willCacheResponse:completionHandler:

函數聲明:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler

函數做用:

詢問data task或上傳任務(upload task)是否緩存response。

函數討論:

當task接收到全部指望的數據後,session會調用此代理方法。若是你沒有實現該方法,那麼就會使用建立session時使用的configuration對象決定緩存策略。這個代理方法最初的目的是爲了阻止緩存特定的URLs或者修改NSCacheURLResponse對象相關的userInfo字典。

該方法只會當request決定緩存response時候調用。做爲準則,responses只會當如下條件都成立的時候返回緩存:

  • 該request是HTTP或HTTPS URL的請求(或者你自定義的網絡協議,而且確保該協議支持緩存)
  • 確保request請求是成功的(返回的status code爲200-299)
  • 返回的response是來自服務器端的,而非緩存中自己就有的
  • 提供的NSURLRequest對象的緩存策略要容許進行緩存
  • 服務器返回的response中與緩存相關的header要容許緩存
  • 該response的大小不能比提供的緩存空間大太多(好比你提供了一個磁盤緩存,那麼response大小必定不能比磁盤緩存空間還要大5%)

函數實現:

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
{
    NSCachedURLResponse *cachedResponse = proposedResponse;
    // 自定義方法,你能夠什麼都不作,返回原始的cachedResponse,或者使用修改後的cachedResponse
    // 固然,你也能夠返回NULL,這就意味着不須要緩存Response
    if (self.dataTaskWillCacheResponse) {
        cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
    }

    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}

3.4 NSURLSessionDownloadDelegate

3.4.1 - URLSession:downloadTask:didFinishDownloadingToURL:(必須實現)

函數聲明:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

函數做用:

告訴代理,該下載任務已完成。

函數實現:

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    if (self.downloadTaskDidFinishDownloading) {
        // 自定義函數,根據從服務器端獲取到的數據臨時地址location等參數構建出你想要將臨時文件移動的位置
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        // 若是fileURL存在的話,表示用戶但願把臨時數據存起來
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            // 將位於location位置的文件所有移到fileURL位置
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
            if (error) {
                // 若是移動文件失敗,就發送AFURLSessionDownloadTaskDidFailToMoveFileNotification
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            }

            return;
        }
    }
    // 這一步比較詭異,感受有重複的嫌疑。或許是爲了兼容之前代碼吧
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}

3.4.2 - URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:

函數聲明:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite

函數做用:

週期性地通知下載進度。

函數實現:

// bytesWritten 表示自上次調用該方法後,接收到的數據字節數
// totalBytesWritten 表示目前已經接收到的數據字節數
// totalBytesExpectedToWrite 表示指望收到的文件總字節數,是由Content-Length header提供。若是沒有提供,默認是NSURLSessionTransferSizeUnknown
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    // 自定義
    if (self.downloadTaskDidWriteData) {
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}

3.4.3 - URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:

函數聲明:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes

函數做用:

告訴代理,下載任務從新開始下載了。

函數討論:

若是一個resumable(不是很會翻譯)下載任務被取消或者失敗了,你能夠請求一個resumeData對象(好比在userInfo字典中經過NSURLSessionDownloadTaskResumeData這個鍵來獲取到resumeData)並使用它來提供足夠的信息以從新開始下載任務。隨後,你可使用resumeData做爲downloadTaskWithResumeData:或downloadTaskWithResumeData:completionHandler:的參數。

當你調用這些方法時,你將開始一個新的下載任務。一旦你繼續下載任務,session會調用它的代理方法URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:其中的downloadTask參數表示的就是新的下載任務,這也意味着下載從新開始了。

函數實現:

// fileOffset若是文件緩存策略或者最後文件更新日期阻止重用已經存在的文件內容,那麼該值爲0。
// 不然,該值表示已經存在磁盤上的,不須要從新獲取的數據——— 這是斷點續傳啊!
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
    if (self.downloadTaskDidResume) {
        self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
    }
}

4. 還沒結束


這一篇因爲篇幅緣由,不想繼續寫下去了。後續內容,包括AFURLSessionManagerTaskDelegate、uploadTask、downloadTask、測試等零散的東西準備放在下一篇進行學習。不過,一切還沒結束,AFNetworking源碼之路還很長。

5. 參考文章


相關文章
相關標籤/搜索