iOS基礎深入補完計劃--NSURLSession使用詳解(附Demo)

目錄

  • 前言
  • API
  • Demo

前言

本文主要是把NSURLSession以及NSURLSessionTask相關的代理方法全部整理了一下。
旨在大體了解在一個iOS網絡請求中、一個任務究竟經理了什么。
而我們、又能做些什么。


API

大概的結構長這樣


API結構
#pragma mark - NSURLSessionDelegate
#pragma mark 會話總代理
#pragma mark 通知>>session被關閉
//[session invalidateAndCancel]或者[session finishTasksAndInvalidate]
//session被關閉時調用、持有的delegate將被清空
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error {
    NSLog(@"NSURLSessionDelegate:::通知>>session被關閉");
}

#pragma mark 詢問>>服務器客戶端配合驗證--會話級別
/*
 參考:
 http://www.lxweimin.com/p/2642e31919e7
 https://www.2cto.com/kf/201604/504149.html
 https://blog.csdn.net/jingcheng345413/article/details/65437649
 NSURLAuthenticationChallenge 類中最重要的一個屬性是protectionSpace。
 該屬性是一個 NSURLProtectionSpace 的實例,一個NSURLProtectionSpace對象通過屬性host、isProxy、port、protocol、proxyType和realm代表了請求驗證的服務器端的范圍。
 而NSURLProtectionSpace類的authenticationMethod屬性則指明了服務端的驗證方式,可能的值包括:
 
    challenge.protectionSpace {
        // 默認
        NSURLAuthenticationMethodDefault
        // 基本的 HTTP 驗證,通過 NSURLCredential 對象提供用戶名和密碼。
        NSURLAuthenticationMethodHTTPBasic
        // 類似于基本的 HTTP 驗證,摘要會自動生成,同樣通過 NSURLCredential 對象提供用戶名和密碼。
        NSURLAuthenticationMethodHTTPDigest
        // 不會用于 URL Loading System,在通過 web 表單驗證時可能用到。
        NSURLAuthenticationMethodHTMLForm
 
        <<<<<***************>>>>>
        //Negotiate(協商,Kerberos or NTLM)
        NSURLAuthenticationMethodNegotiate
        //NTLM(WindowsNT使用的認證方式
        NSURLAuthenticationMethodNTLM
        // 驗證客戶端的證書
        NSURLAuthenticationMethodClientCertificate
        // 指明客戶端要驗證服務端提供的證書
        NSURLAuthenticationMethodServerTrust
    }
 
 其中后四個為會話級別驗證
 將會優先調用會話級別驗證、如果未實現再調用任務界別驗證。
 */
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
    NSLog(@"NSURLSessionDelegate:::詢問>>服務器客戶端配合驗證--會話級別");
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling,nil);
}

#pragma mark 通知>>所有后臺下載任務全部完成
//必須在backgroundSessionConfiguration 并且在后臺完成時才會調用
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    NSLog(@"NSURLSessionDelegate:::通知>>所有后臺下載任務全部完成");
}


#pragma mark - NSURLSessionTaskDelegate
#pragma mark 任務總代理

#pragma mark 通知>>延時任務被調用
/*
    當設置了earliestBeginDate屬性
    (需要注意這個屬性對于非后臺任務并不有效、而且不能保證定時執行、只能保證不會在指定日期之前執行)
    的NSURLSessionTask被延遲調用的、會走這里
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
willBeginDelayedRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLSessionDelayedRequestDisposition disposition, NSURLRequest * _Nullable newRequest))completionHandler {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>延時任務被調用");
    /*
        typedef NS_ENUM(NSInteger, NSURLSessionDelayedRequestDisposition) {
        NSURLSessionDelayedRequestContinueLoading = 0,  //使用原始請求、參數忽略
        NSURLSessionDelayedRequestUseNewRequest = 1,    //使用新請求
        NSURLSessionDelayedRequestCancel = 2,   //取消任務、參數忽略
        }
     */
    completionHandler(NSURLSessionDelayedRequestContinueLoading,request);
}


#pragma mark 通知>>網絡受限導致任務進入等待
/*
    如果NSURLSessionConfiguration的waitsForConnectivity屬性為true
    并且由于網絡不通(并不是并發受限)沒有被立即發出時
    此方法最多只能在每個任務中調用一次、并且僅在連接最初不可用時調用。
    它永遠不會被調用后臺會話,因為這些會話會忽略waitsForConnectivity。
 
 */
- (void)URLSession:(NSURLSession *)session taskIsWaitingForConnectivity:(NSURLSessionTask *)task {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>網絡受限導致任務進入等待");
}

#pragma mark 準備開始請求、詢問是否重定向
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
    NSLog(@"NSURLSessionTaskDelegate:::詢問>>是否重定向");
    completionHandler(request);
}

#pragma mark 詢問>>服務器需要客戶端配合驗證--任務級別
//會話級別除非未實現對應代理、否則不會調用任務級別驗證方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
    NSLog(@"NSURLSessionTaskDelegate:::詢問>>服務器需要客戶端配合驗證--任務級別");
    NSURLCredential * cre =[NSURLCredential credentialWithUser:@"kirito_song" password:@"psw" persistence:NSURLCredentialPersistenceNone];
    completionHandler(NSURLSessionAuthChallengeUseCredential,cre);
}

#pragma mark 詢問>>流任務的方式上傳--需要客戶端提供數據源
/* 當任務需要新的請求主體流發送到遠程服務器時,告訴委托。
 這種委托方法在兩種情況下被調用:
 1、如果使用uploadTaskWithStreamedRequest創建任務,則提供初始請求正文流:
 2、如果任務因身份驗證質詢或其他可恢復的服務器錯誤需要重新發送包含正文流的請求,則提供替換請求正文流。
 注:如果代碼使用文件URL或NSData對象提供請求主體,則不需要實現此功能。
 */

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
 needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler {
    NSLog(@"NSURLSessionTaskDelegate:::詢問>>數據流的方式上傳--需要客戶端提供數據源");
}

#pragma mark 通知>>上傳進度
/* 定期通知代理向服務器發送主體內容的進度。*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>上傳進度");
}



#pragma mark 通知>>任務信息收集完成
/*
 對發送請求/DNS查詢/TLS握手/請求響應等各種環節時間上的統計. 更易于我們檢測, 分析我們App的請求緩慢到底是發生在哪個環節, 并對此進行優化提升我們APP的性能.
 
 NSURLSessionTaskMetrics對象與NSURLSessionTask對象一一對應. 每個NSURLSessionTaskMetrics對象內有3個屬性 :
 
 taskInterval : task從開始到結束總共用的時間
 redirectCount : task重定向的次數
 transactionMetrics : 一個task從發出請求到收到數據過程中派生出的每個子請求, 它是一個裝著許多NSURLSessionTaskTransactionMetrics對象的數組. 每個對象都代表下圖的一個子過程
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>任務信息收集完成");
    NSLog(@"::::::::::::相關訊息::::::::::::\n總時間:%@\n,重定向次數:%zd\n,派生的子請求:%zd",metrics.taskInterval,metrics.redirectCount,metrics.transactionMetrics.count);
}

#pragma mark 通知>>任務完成
//無論成功、失敗或者取消
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>任務完成");
}

#pragma mark - NSURLSessionDataDelegate
#pragma mark 數據任務代理
#pragma mark 通知>>服務器返回響應頭
#pragma mark 詢問>>下一步操作
//服務器返回響應頭、詢問下一步操作(取消操作、普通傳輸、下載、數據流傳輸)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    NSLog(@"NSURLSessionDataDelegate:::通知>>服務器返回響應頭。詢問>>下一步操作");
    completionHandler(NSURLSessionResponseAllow);
//    completionHandler(NSURLSessionResponseBecomeDownload);
//    completionHandler(NSURLSessionResponseBecomeStream);
}

#pragma mark 通知>>數據任務已更改為下載任務
//你可以通過上面的 completionHandler(NSURLSessionResponseBecomeDownload);進行測試
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask {
    NSLog(@"NSURLSessionDataDelegate:::通知>>數據任務已更改為下載任務");
}

#pragma mark 通知>>數據任務已更改為流任務
//你可以通過上面的 completionHandler(NSURLSessionResponseBecomeStream);進行測試
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask {
    NSLog(@"NSURLSessionDataDelegate:::通知>>數據任務已更改為下載任務");
}

#pragma mark 通知>>服務器成功返回數據
//已經收到了一些(大數據可能多次調用)數據
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data {
    NSLog(@"NSURLSessionDataDelegate:::通知>>服務器成功返回數據");
}

#pragma mark 詢問>>是否把Response存儲到Cache中
//任務是否應將響應存儲在緩存中
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler {
    NSLog(@"NSURLSessionDataDelegate:::詢問>>是否把Response存儲到Cache中");
    NSCachedURLResponse * res = [[NSCachedURLResponse alloc]initWithResponse:proposedResponse.response data:proposedResponse.data userInfo:nil storagePolicy:NSURLCacheStorageNotAllowed];
    completionHandler(res);
}


#pragma mark - NSURLSessionDownloadDelegate
#pragma mark 下載任務代理
#pragma mark 通知>>下載任務已經完成
//location 臨時文件的位置url 需要手動移動文件至需要保存的目錄
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"NSURLSessionDownloadDelegate:::通知>>下載任務已經完成");
}

#pragma mark 通知>>下載任務進度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    
    NSLog(@"NSURLSessionTaskDelegate:::通知>>下載任務進度");
    
}

#pragma mark 通知>>下載任務已經恢復下載
//filrOffest 已經下載的文件大小  expectedTotalBytes預期總大小
/*
    你可以通過 [session downloadTaskWithResumeData:resumeData]之類的方法來重新恢復一個下載任務
    resumeData在下載任務失敗的時候會通過error.userInfo[NSURLSessionDownloadTaskResumeData]來返回以供保存
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>下載任務已經恢復下載");
}


#pragma mark - NSURLSessionStreamDelegate
#pragma mark 流任務代理
#pragma mark 通知>>數據流的連接中讀數據的一邊已經關閉
- (void)URLSession:(NSURLSession *)session readClosedForStreamTask:(NSURLSessionStreamTask *)streamTask {
    NSLog(@"NSURLSessionStreamDelegate:::通知>>數據流的連接中讀數據的一邊已經關閉");
}

#pragma mark 通知>>數據流的連接中寫數據的一邊已經關閉
- (void)URLSession:(NSURLSession *)session writeClosedForStreamTask:(NSURLSessionStreamTask *)streamTask {
    NSLog(@"NSURLSessionStreamDelegate:::通知>>數據流的連接中寫數據的一邊已經關閉");
}

#pragma mark 通知>>系統已經發現了一個更好的連接主機的路徑
- (void)URLSession:(NSURLSession *)session betterRouteDiscoveredForStreamTask:(NSURLSessionStreamTask *)streamTask {
    NSLog(@"NSURLSessionStreamDelegate:::通知>>系統已經發現了一個更好的連接主機的路徑");
}

#pragma mark 通知>>流任務已完成
- (void)URLSession:(NSURLSession *)session streamTask:(NSURLSessionStreamTask *)streamTask
didBecomeInputStream:(NSInputStream *)inputStream
      outputStream:(NSOutputStream *)outputStream {
    NSLog(@"NSURLSessionStreamDelegate:::通知>>流任務已完成");
}


Demo

簡單的寫了幾個具體用法、以更直觀的觀測代理方法的運行以及順序


文本請求.gif
圖片請求.gif
下載任務&&斷點續傳.gif

Demo可以自取


最后

本文主要是自己的學習與總結。如果文內存在紕漏、萬望留言斧正。如果不吝賜教小弟更加感謝。


參考

NSURLSession簡介
NSURLSession使用注意事項
NSURLSession使用說明及后臺工作流程分析
iOS使用NSURLSession進行下載(包括后臺下載,斷點下載)
[iOS-Foundation] Networking Authentication
iOS網絡請求認證挑戰
網絡請求之NSURLSession(api篇)
深入了解NSURLSession
緩存

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容