前言
本文主要是結合官方文件, 挖掘NSURLSession的類層次結構及其聯絡, 總結出關於NSURLSession的一些關鍵點及其用法.
關於NSURLSession為什麼能取代NSURLConnection, 其優勢是什麼, 及其NSURLSession API的概述, 見
關於ATS, HTTP/2, 以及iOS9 NSURLSession新特性 : sharedCookies, streamTask和taskMetrics, 見
以上兩篇文章都是我看wwdc視訊然後總結出來的文章, 大家感興趣的可以先了解了解. 如果不想知道那麼多, 只想知道怎麼用NSURLSession, 那就直接看本文的正文.
*瞭解URL Loading System
目錄
-
介紹NSURLSession相關類
-
身份驗證和自定義TLS
-
App Transport Security
-
NSURLSession 工作流程
-
後臺傳輸及其用法
-
NSURLSession API
-
其他一些注意點
NSURLSession
NSURLSession
- 支援data, ftp, http(s)協議, 同時支援代理伺服器和socks閘道器.
- 支援http/1.1, http/2, spdy協議, 但同時需要伺服器支援ALPN和NPN.
ALPN與NPN
- ALPN(Application Layer Protocol Negotiation,應用層協議協商)
- NPN(Next Protocol Negotiation,下一代協議協商)
NPN是服務端傳送它支援的HTTP協議列表, 供客戶端選擇; 而ALPN則相反, 由客戶端傳送它支援的HTTP協議列表, 供服務端選擇. 如果缺少NPN/ALPN其中一個, 則無法使用HTTP/2通訊. 具體請見為什麼我們應該儘快支援 ALPN.
NSURLSession相關類為 :
- NSURLSession
- NSURLSessionConfiguration
- NSURLSessionDelegate
- NSURLSessionTask
- NSURLSessionTaskMetrics
- NSURLSessionTaskTransactionMetrics
他們相互的關係如下 :
NSURLSession
session分為 :
- 全域性共享單例session :
NSURLSession sharedSession
, 有一定的侷限性 - 自定義session : 自定義配置檔案, 設定代理, 大部分時間我們都是用這個
- 後臺session : 也是自定義session的一種, 只是他專門用於做後臺上傳/下載任務
session為哪一種型別完全由其內部的Configuration而定.
NSURLSessionConfiguration
配置分為 :
- defaultSessionConfiguration : 系統預設
- ephemeralSessionConfiguration : 僅記憶體快取, 不做磁碟快取的配置
- backgroundSessionConfiguration : 這裡需要指定一個identifier, identifier用來後臺重連session物件. (做後臺上傳/下載就是這個config)
另外, 我們還可以給Configuration物件再自定義一些屬性, 例如每埠的最大併發HTTP請求數目, 以及是否允許蜂窩網路, 請求快取策略, 請求超時, cookies/證書儲存策略等等
NSURLSessionDelegate
session管理的一組tasks共享一個代理, 不想實現代理方法時, 代理傳nil即可.
代理協議分為 :
NSURLSessionDelegate
: session-level的代理方法NSURLSessionTaskDelegate
: task-level面向all的代理方法NSURLSessionDataDelegate
: task-level面向data和upload的代理方法NSURLSessionDownloadDelegate
: task-level的面向download的代理方法NSURLSessionStreamDelegate
: task-level的面向stream的代理方法
NSURLSessionTask
session task型別分為 :
NSURLSessionTask
: Task的抽象基類NSURLSessionDataTask
: 以NSData的形式接收一個URLRequest的內容NSURLSessionUploadTask
: 上傳NSData或者本地磁碟中的檔案, 完成後以NSData的形式接收一個URLRequest的響應NSURLSessionDownloadTask
: 下載完成後返回臨時檔案在本地磁碟的URL路徑NSURLSessionStreamTask
: 用於建立一個TCP/IP連線
NSURLSessionTaskMetrics 和 NSURLSessionTaskTransactionMetrics
對傳送請求/DNS查詢/TLS握手/請求響應等各種環節時間上的統計. 更易於我們檢測, 分析我們App的請求緩慢到底是發生在哪個環節, 並對此進行優化提升我們APP的效能.
NSURLSessionTaskMetrics物件與NSURLSessionTask物件一一對應. 每個NSURLSessionTaskMetrics物件內有3個屬性 :
- taskInterval : task從開始到結束總共用的時間
- redirectCount : task重定向的次數
- transactionMetrics : 一個task從發出請求到收到資料過程中派生出的每個子請求, 它是一個裝著許多NSURLSessionTaskTransactionMetrics物件的陣列. 每個物件都代表下圖的一個子過程
API很簡單, 就一個方法 : - (void)URLSession: task: didFinishCollectingMetrics:
, 當收集完成的時候就會呼叫該方法.
身份驗證和自定義TLS
- 當一個伺服器請求身份驗證或TLS握手期間需要提供證書的話, URLSession會呼叫他的代理方法
URLSession:didReceiveChallenge:completionHandler:
去處理. - 如果你沒有實現該代理方法, URLSession就會這麼做 :
- 使用身份認證資訊作為請求URL的一部分(如果可用的話)
- 在使用者的keychain中查詢網路密碼和證書(in macOS), 在app的keychain中查詢(in iOS)
- 如果證書還是不可用或伺服器拒絕該證書, 就會繼續缺少身份認證的連線.
- 對於HTTP(S)連線, 請求失敗並返回一個狀態碼, 可能會提供一些替代的內容, 例如一個私人網站的公共網頁.
- 對於其他URL型別(如FTP等), 則連線請求失敗, 直接返回錯誤資訊
App Transport Security
從iOS9開始支援ATS, 且預設ATS只支援傳送HTTPS請求, 不允許傳送不安全的HTTP請求. 如果使用者需要傳送HTTP請求需要在info.plist
中配置一些東西.
詳情在文章開頭的iOS9 ATS HTTP/2 NSURLSession中說得很詳細, 想了解的可以進去閱讀.
NSURLSession 工作流程
那麼如何使用NSURLSession像從前用NSURLConnection那樣傳送一個請求呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 設定配置 NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; /** 設定其他配置屬性 **/ // 代理佇列 NSOperationQueue *queue = [NSOperationQueue mainQueue]; // 建立session NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:queue]; // 利用session建立n個task NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"]]; // 開始 [task resume]; |
然後就可以在代理方法中處理各種事情了. 簡單吧? 下面分task說明代理方法的呼叫情況..
身份驗證或TLS握手
這是所有task都必須經歷的一個過程. 當一個伺服器請求身份驗證或TLS握手期間需要提供證書的話, URLSession會呼叫他的代理方法URLSession:didReceiveChallenge:completionHandler:
去處理., 另外, 如果連線途中收到伺服器返回需要身份認證的response, 也會呼叫該代理方法.
重定位response
這也是所有task都有可能經歷的一個過程, 如果response是HTTP重定位, session會呼叫代理的URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:
方法. 這裡需要呼叫completionHandler告訴session是否允許重定位, 或者重定位到另一個URL, 或者傳nil表示重定位的響應body有效並返回. 如果代理沒有實現該方法, 則允許重定位直到達到最大重定位次數.
DataTask
- 對於一個data task來說, session會呼叫代理的
URLSession:dataTask:didReceiveResponse:completionHandler:
方法, 決定是否將一個data dask轉換成download task, 然後呼叫completion回撥繼續接收data或下載data.- 如果你的app選擇轉換成download task, session會呼叫代理的
URLSession:dataTask:didBecomeDownloadTask:
方法並把新的download task物件以方法引數的形式傳給你. 之後代理不會再收到data task的回撥而是轉為收到download task的回撥
- 如果你的app選擇轉換成download task, session會呼叫代理的
- 在伺服器傳輸資料給客戶端期間, 代理會週期性地收到
URLSession:dataTask:didReceiveData:
回撥 - session會呼叫
URLSession:dataTask:willCacheResponse:completionHandler:
詢問你的app是否允許快取. 如果代理不實現這個方法的話, 預設使用session繫結的Configuration的快取策略.
DownloadTask
- 對於一個通過
downloadTaskWithResumeData:
建立的下載任務, session會呼叫代理的URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
方法. - 在伺服器傳輸資料給客戶端期間, 呼叫
URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:
給使用者傳資料- 當使用者暫停下載時, 呼叫
cancelByProducingResumeData:
給使用者傳已下好的資料. - 如果使用者想要恢復下載, 把剛剛的resumeData以引數的形式傳給
downloadTaskWithResumeData:
方法建立新的task繼續下載.
- 當使用者暫停下載時, 呼叫
- 如果download task成功完成了, 呼叫
URLSession:downloadTask:didFinishDownloadingToURL:
把臨時檔案的URL路徑給你. 此時你應該在該代理方法返回以前讀取他的資料或者把檔案持久化.
UploadTask
上傳資料去伺服器期間, 代理會週期性收到URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
回撥並獲得上傳進度的報告.
StreamTask
如果任務的資料是由一個stream發出的, session就會呼叫代理的URLSession:task:needNewBodyStream:方法去獲取一個NSInputStream物件並提供一個新請求的body data.
task completion
任何task完成的時候, 都會呼叫URLSession:task:didCompleteWithError:
方法, error有可能為nil(請求成功), 不為nil(請求失敗)
- 請求失敗, 但是該任務是可恢復下載的, 那麼error物件的userInfo字典裡有一個
NSURLSessionDownloadTaskResumeData
對應的value, 你應該把這個值傳給downloadTaskWithResumeData:
方法重新恢復下載 - 請求失敗, 但是任務無法恢復下載, 那麼應該重新建立一個下載任務並從頭開始下載.
- 因為其他原因(如伺服器錯誤等等), 建立並恢復請求.
Note
NSURLSession不會收到伺服器傳來的錯誤, 代理只會收到客戶端出現的錯誤, 例如無法解析主機名或無法連線上主機等等. 客戶端錯誤定義在URL Loading System Error Codes. 服務端錯誤通過HTTP狀態法進行傳輸, 詳情請看NSHTTPURLResponse和NSURLResponse類.
銷燬session
如果你不再需要一個session了, 一定要呼叫它的invalidateAndCancel
或finishTasksAndInvalidate
方法. (前者是取消所有未完成的任務然後使session失效, 後者是等待正在執行的任務完成之後再使session失效). 否則的話, 有可能造成記憶體洩漏. 另外, session失效後會呼叫URLSession:didBecomeInvalidWithError:
方法, 之後session釋放對代理的強引用.
Background Transport
需要注意的是, 在後臺session中, 一些代理方法將失效. 下面說一些使用後臺session的注意點 :
- 後臺session必須提供一個代理處理突發事件
- 只支援HTTP(S)協議. 其他協議都不可用.
- 只支援上傳/下載任務, data任務不支援.
- 後臺任務有數量限制
- 當任務數量到達系統指定的臨界值的時候, 一些後臺任務就會被取消. 也就是說, 一個需要長時間上傳/下載的任務很可能會被系統取消然後有可能過一會再重新開始, 所以支援斷點續傳很重要.
- 如果一個後臺傳輸任務是在app在後臺的時候開啟的, 那麼這個任務很可能會出於對效能的考慮隨時被系統取消掉. . (相當於session的Configuration物件的discretionary屬性為true.)
後臺session限制確實很多, 所以儘可能使用前臺session做事情.
Note
後臺session最好用來傳輸一些支援斷點續傳大檔案. 或對這個過程進行一些針對性的優化
- 最好把檔案先壓縮成zip/tar等壓縮檔案再上傳/下載.
- 把大檔案按資料段分別傳送, 傳送完之後服務端再把資料拼接起來.
- 上傳的時候服務端應該返回一個識別符號, 這樣可以追蹤傳輸的狀態, 及時做出傳輸的調整
- 增加一個web代理伺服器中間層, 以促進上述的優化
Usage
那麼如何使用這個後臺傳輸呢?
- 建立一個後臺session
12NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.Jerry4me.backgroundSessionIdentifier"];_backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; - 建立一個upload or download task
123456NSURL *URL = [NSURL URLWithString:@"http://www.bz55.com/uploads/allimg/140402/137-140402153504.jpg"];NSURLRequest *request = [NSURLRequest requestWithURL:URL];self.task = [self.session downloadTaskWithRequest:request];/**注意 : 後臺任務不能使用帶有completionHandler的方法建立 **//**注意 : 如果任務只想在app進入後臺後才處理, 那麼可不呼叫[task resume]主動執行, 待程式進入後臺後會自動執行 **/
- 我們等下載到一半後進入後臺, 開啟App Switcher過一會可以發現, 圖片下載完之後就會顯示在應用程式上. 方法呼叫順序為 : 下面四個方法全部都是app在後臺時呼叫的
1 2 3 4 5 6 |
2017-03-24 14:17:09.458415 JRBgSessionDemo[2766:1080861] 下載中 - 58% 2017-03-24 14:17:09.567957 JRBgSessionDemo[2766:1080861] 下載中 - 59% 2017-03-24 14:17:16.916830 JRBgSessionDemo[2766:1080828] -[AppDelegate application:handleEventsForBackgroundURLSession:completionHandler:] 2017-03-24 14:17:16.951185 JRBgSessionDemo[2766:1080977] -[DownloadViewController URLSession:downloadTask:didFinishDownloadingToURL:] 2017-03-24 14:17:16.953951 JRBgSessionDemo[2766:1080977] -[DownloadViewController URLSession:task:didCompleteWithError:] 2017-03-24 14:17:16.954574 JRBgSessionDemo[2766:1080977] -[DownloadViewController URLSessionDidFinishEventsForBackgroundURLSession:] |
總結後臺傳輸
- 儘量用真機進行除錯, 模擬器會跳過某一兩個方法
- 只能進行upload/download task, 不能進行data task
- 不能使用帶completionHandler的方法建立task, 否則程式直接掛掉
- Applecation裡的completionHandler必須儲存起來, 等你處理完所有事情之後再呼叫告訴系統可以進行Snapshot和掛起app了
- 後臺下載最好支援斷點續傳, 因為任務有可能會被系統主動取消(例如系統效能下降了, 資源不夠用的情況下)
後臺傳輸的Demo在文章頭部的地方, 也可以點這裡進去
NSURLSession API
-
建立Session
+ sessionWithConfiguration:
: 建立一個指定配置的session+ sessionWithConfiguration:delegate:delegateQueue:
: 建立一個指定配置, 代理和代理方法執行佇列的sessionsharedSession
: 返回session單例
-
配置Session
configuration
: 配置delegate
: 代理物件delegateQueue
: 代理方法的執行佇列sessionDescription
: app定義的對於該session的描述
-
新增data任務
- dataTaskWithURL:
: 獲取指定URL內容- dataTaskWithURL:completionHandler:
: 獲取指定URL內容, 在completionHandler中處理資料. 該方法會繞過代理方法(除了身份認證挑戰的代理方法)- dataTaskWithRequest:
: 獲取指定URLRequest內容- dataTaskWithRequest:completionHandler:
: 獲取指定URLRequest內容, 在completionHandler中處理資料. 該方法會繞過代理方法(除了身份認證挑戰的代理方法)
-
新增download任務
- downloadTaskWithURL:
: 下載指定URL內容- downloadTaskWithURL:completionHandler:
: 下載指定URL內容, 在completionHandler中處理資料. 該方法會繞過代理方法(除了身份認證挑戰的代理方法)- downloadTaskWithRequest:
: 下載指定URLRequest內容- downloadTaskWithRequest:completionHandler:
: 下載指定URLRequest內容, 在completionHandler中處理資料. 該方法會繞過代理方法(除了身份認證挑戰的代理方法)- downloadTaskWithResumeData:
: 建立一個之前被取消/下載失敗的download task- downloadTaskWithResumeData:completionHandler:
: 建立一個之前被取消/下載失敗的download task, 在completionHandler中處理資料. 該方法會繞過代理方法(除了身份認證挑戰的代理方法)
-
新增upload任務
- uploadTaskWithRequest:fromData:
: 通過HTTP請求傳送data給指定URL- uploadTaskWithRequest:fromData:completionHandler:
: 通過HTTP請求傳送data給指定URL, 在completionHandler中處理資料. 該方法會繞過代理方法(除了身份認證挑戰的代理方法)- uploadTaskWithRequest:fromFile:
: 通過HTTP請求傳送指定檔案給指定URL- uploadTaskWithRequest:fromFile:completionHandler:
: 通過HTTP請求傳送指定檔案給指定URL, 在completionHandler中處理資料. 該方法會繞過代理方法(除了身份認證挑戰的代理方法)uploadTaskWithStreamedRequest
: 通過HTTP請求傳送指定URLRequest資料流給指定URL
-
新增stream任務
- streamTaskWithHostName:port:
: 通過給定的域名和埠建立雙向TCP/IP連線- streamTaskWithNetService:
: 通過給定的network service建立雙向TCP/IP連線
-
管理session
finishTasksAndInvalidate
: 任務全部完成後銷燬sessionflushWithCompletionHandler:
: 清除硬碟上的cookies和證書, 清理暫時的快取, 確保未來能響應一個新的TCP請求getTasksWithCompletionHandler:
: 非同步呼叫session中所有upload, download, data tasks的completion回撥.invalidateAndCancel
: 取消所有未完成的任務並銷燬sessionresetWithCompletionHandler:
: 清空cookies, 快取和證書儲存, 移除所有磁碟檔案, 清理正在執行的下載任務, 確保未來能響應一個新的socket請求
API總結
所有建立task的方法, 只要帶有completionHandler這個引數的, 均表示為請求過程中不會觸發代理方法. 所有不帶有completionHandler這個引數的, 均會走代理方法流程.
如果你實現了URLSession:didReceiveChallenge:completionHandler:
方法又沒有在該方法呼叫completionHandler, 請求就會遭到阻塞
斷點續傳
- 下載失敗/暫停/被取消, 可以通過task的
- cancelByProducingResumeData:
方法儲存已下載的資料, 然後呼叫session的downloadTaskWithResumeData:
方法, 觸發代理的URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
方法
Something else Important
NSCopying Behavior
session, task和configuration物件都支援copy操作 :
- session/task copy : 返回session物件本身
- configuration copy : 返回一個無法修改(immutable)的物件.
執行緒安全
URLSession 的API全部都是執行緒安全的. 你可以在任何執行緒上建立session和tasks, task會自動排程到合適的代理佇列中執行.
Warning
後臺傳輸的代理方法
URLSessionDidFinishEventsForBackgroundURLSession:
可能會在其他執行緒中被呼叫. 在該方法中你應該回到主執行緒然後呼叫completion handler去觸發AppDelegate中的application:handleEventsForBackgroundURLSession:completionHandler:
方法.
常量
NSURLSession-Specific NSError userInfo Dictionary Keys
: NSURLSession API 中出現的NSError的userInfo的keysBackground Task Cancellation reasons
: 指示系統為什麼取消了你的後臺任務的理由Transfer Size Constant
: 指示一個未知傳輸大小的常量
參考文件
NSURLSession – Foundation
WWDC 2013 – Session 204 – What’s New with Multitasking
WWDC 2013 – Session 705 – What’s New in Foundation Networking
WWDC 2015 – Session 711 – Networking with NSURLSession
WWDC 2016 – Session 711 – NSURLSession: New Features and Best Practices
Github : Jerry4me, Demo : JRBgSessionDemo
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式