NSURLSession
類及其相關類為下載內容提供了接口。這個 API 提供了一系列豐富的代理方法來支持授權,而且讓你的 APP 在后臺被掛起時也能繼續下載。
通過代理服務器的和 SOCKS 網關為用戶配置好系統設置,NSURLSession
類完全支持 data
, file
, ftp
, http
, https
類型的超鏈接。你也可以添加一些支持你自己定制的網絡協議或超鏈接(專門為你自己的 APP 所用)
重大說明!
NSURLSession
的 API 有很多你在看文檔時可能感覺并不重要,但實際上卻以很復雜的方式來共同發揮作用的類。用這個 API 之前,你應該看一下 URL Session Programming Guide 來了解這些類之間相互調用的一些概念。
通過 NSURLSession
的 API,你的 APP可以創建一個或者多個事務,其中的每一個事務都用來協助一組相關的數據來傳輸任務。比如,如果你在寫一個瀏覽器,你的 APP 可能在每一欄或是每一個窗口都創建了一個事務,可能一個用來進行交互,一個用來后臺下載。在每一個事務中,你的 APP 都添加了一些了的任務,其中的每一個任務就代表一個請求或是一個特定的 URL(必要時進行重定向)
給定 URL事務的這些任務共享一個事務配置 session,這個對象定義好了連接行為,比如通往同一個主機的相似連接的最大連接數,不管是通過蜂窩移動網絡還是別的方式來允許連接。session 的行為一部分是由創建這個配置對象時你調用的方法來決定:
單例 shared session (沒有配置對象)用于普通請求。它像你自己創建的session一樣可定制,但是如果你的請求有限的話它是一個很好的起點,通過調用 sharedSession 來獲取這個 session??催@個方法的文檔來得到它的一些限制信息。
Default sessions 的行為和 shared session 差不多(除非你自己進行了深度定制),但你必須要通過代理方法得到增量數據。調用 NSURLSessionConfiguration 中的 defaultSessionConfiguration 方法來創建 Default session。
Ephemeral sessions和Default sessions 很相似,但是不寫入 caches,cookies, 或credentials。
調用 NSURLSessionConfiguration 中的 ephemeralSessionConfiguration 方法來創建 Ephemeral session。Background sessions 可以在你的 APP 在后臺被掛起時進行上傳或下載。調用 NSURLSessionConfiguration 中的 backgroundSessionConfiguration: 方法來創建 background session。
session 配置對象還包含了對 URL cache 和 cookie 存儲對象發起請求并處理相應的引用,具體取決于配置和請求類型。
當多種類型的事件發生時(比如授權失敗時,數據傳輸到服務器時,數據準備好緩存時等等)session 中的任務也會共享一個通用的代理來讓你提供和獲取相關信息。對于所有的后臺下載和上傳,你必須提供一個遵從 NSURLSessionDownloadDelegate 的 OC 協議。否則,如果你不需要任何由代理提供的特性,在你創建 session 時就給 delegate 傳 nil 來調用這個 API。
重大說明!
session 對你的 delegate 始終保持強引用,除非你的 app 退出或者 session已明確失效。如果你沒有使 session 失效,你的 app 將會內存溢出,直到它終止。
在 session 中,你創建了可上傳數據到服務器的任務,然后通過磁盤上的文件,或是內存中的一個或多個 NSData
對象來獲取數據。 NSURLSession
的 API 提供了三種類型的任務:
-
data tasks 通過
NSData
對象來發送和接受數據。 data tasks 用于與服務器進行簡短頻繁的請求。 - Upload tasks 和 data tasks 類似,但是他們也可以發送數據(一般是通過文件的形式),也支持 app 后臺上傳。
- Download tasks 通過文件的形式來獲取數據,并且支持 app 后臺上傳和下載。
和大多數網絡接口一樣,NSURLSession
是高度異步的。它通過兩種方式中的其中一種來返回數據給 app,取決于你調用的方法:
- 當傳輸成功或是發生錯誤時調用 completion handler block;
- 當收到數據或是傳輸完成時調用 session 的代理方法。
除了通過代理來傳遞數據,如果你需要通過當前任務的狀態(給出一些狀態警示)做一些程序上的決定, NSURLSession
的 API 統一提供了狀態和進度的屬性,方便查詢。
URL sessions 同樣支持取消,重啟,恢復,掛起任務,而且可以在他們停止的地方恢復掛起、取消、失敗了的下載。
URL Session Class 層級結構
NSURLSession
API 由下面的類組成(下面的縮進顯示的是子類的關系)
- NSURLSession — 一個 session 對象。
- NSURLSessionConfiguration — 用來初始化 session 的配置對象。
-
NSURLSessionTask — 在 session 中表示任務的基類。
- NSURLSessionDataTask — 用來獲取 NSData 對象的 URL 的任務
- NSURLSessionUploadTask — 用來上傳文件,并接收 NSData 對象的 URL 的任務。
- NSURLSessionDownloadTask — 以磁盤臨時文件的形式獲取 URL 的內容的任務。
- NSURLSessionStreamTask — 用來建立 TCP/IP連接的任務。
除此之外,NSURLSession
還提供了4個代理, 訂了代理方法,你的 app 可以實現這些代理方法,提供對 session 和 task 更加細化的控制。
- NSURLSessionDelegate — 可處理 session level 事件的代理
- NSURLSessionTaskDelegate — 可處理和其他所有 task 類型相通的 task level 的方法的代理
- NSURLSessionDataDelegate — 可處理與數據和上傳任務相關的 task level 的方法的代理
- NSURLSessionDownloadDelegate — 處理與下載任務相關的 task level 的方法的代理
- NSURLSessionStreamDelegate — 處理與數據流相關的 task level 的方法的代理
最終,NSURLSession
的 API 使用了很多類,這些類也同樣使用了其他的 API,比如 NSURLConnection 和 NSURLDownload。共同使用的類包括:
- NSURL — 包含了 URL 的對象
- NSURLRequest — 封裝與 URL 請求,包括 URL、 請求方法等等相關的元數據。
- NSURLResponse— 封裝服務器相應的請求,如與 Content 的 MIME 類型和長度相關的元數據。
- NSHTTPURLResponse — 給 HTTP請求添加特定的附加元數據,比如 response headers。
-
NSCachedURLResponse — 用來緩存服務器響應的 body data 封裝的一個
NSURLResponse
對象。
身份驗證和 TLS 定制
當服務器要求身份驗證或在 TLS 協商期間提供憑證時,URL Session 通過調用代理方法,讓你可以自定義處理身份驗證或證書驗證。
調用的代理方法取決于你是在處理 task 相關還是 session范圍內的挑戰。Table 1 給出了兩者之間的區別。
**Table 1 ** Session-level and connection-level challenges
對于 task 相關的挑戰, session 會調用代理的
URLSession:task:didReceiveChallenge:completionHandler: 方法。
對于 session 相關的授權挑戰,session 會調用代理的
URLSession:didReceiveChallenge:completionHandler: 方法,如果它不存在,則調用代理的URLSession:task:didReceiveChallenge:completionHandler: 方法。
當請求需要客戶端授權時,如果你沒有實現這些方法,URL Session 會通過下面的方式來嘗試授權:
- 如果 URL中含有授權信息的話,通過請求的 URL 中的授權信息。
- 在用戶的鑰匙串( 在 OS X 中)或是 app 的鑰匙串(在 iOS 中)尋找網絡密碼或是證書。
如果驗證不可用,或是服務器拒絕了驗證,那這個連接將會在沒有授權的情況下繼續。對于 HTTP 和 HTTPs 請求來說,連接會通過一個對應的 HTTP 狀態碼反應嘗試失敗, 也可能會提供可選擇的內容(比如私有網站的公開版本)。對于其他類型的 URL(比如 FTP),將會直接連接失敗。
注
erberos 身份驗證的處理是公開透明的。這里描述的委托方法不適用于 Kerberos 身份驗證。
App 傳輸安全 (ATS)
從 iOS9.0 和 OS X v10.11 開始,一種稱為App 傳輸安全(ATS)的安全特性在所有用作 HTTP 連接的 NSURLSession 中是默認啟用的。ATS 要求 HTTP 連接使用 HTTPS(RFC 2818)。
想了解更多信息,請看
Information Property List Key Reference 中的 NSAppTransportSecurity
使用 URL Session
使用 NSURLSession
類發起一個請求:
- 創建一個 session 配置。若是用作后臺的 session,這個配置必須包括一個 唯一標識(unique identifier)。 存儲這個標識, 當你的 app 崩潰或是被終止或是被掛起時用它來重連 session。
- 創建一個 session, 指定它的配置對象,或是代理。
- 在每個代表一個資源請求的 session 中創建 task 對象。這些 task 對象應該是
NSURLSessionTask—NSURLSessionDataTask, NSURLSessionUploadTask, or NSURLSessionDownloadTask 的子類,具體用哪個取決于你想要做什么。
每個task 開始時都處于掛起狀態。你的 App 調用恢復任務后,它會開始下載其對應的資源。
在你開始一個 task 之后,session 會調用其代理方法,如下:
如果和服務器初始化握手需要一個連接 level 的挑戰(比如SSL 的客戶端證書),NSURLSession 會調用 URLSession:task:didReceiveChallenge:completionHandler: 或是 URLSession:didReceiveChallenge:completionHandler: 代理方法,如前面的
身份驗證和 TLS 定制
所述。 了解更多為 NSURLSession 寫一個授權代理方法的信息,請閱讀 read URL Session Programming Guide.如果這個 task 的數據是由流提供的,NSURLSession 對象將會調用代理的 URLSession:task:needNewBodyStream: 代理方法來獲取一個 提供新請求的 body data 的 NSInputStream 對象。
在初始化上傳到服務器(如果可用的話)的 body 內容期間,代理會階段性接收 URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend: 的回調來報告上傳進度;
服務器發送一個響應;
如果響應表明需要授權,session 會調用代理方法
URLSession:didReceiveChallenge:completionHandler: ,回到步驟2;如果響應是一個 HTTP 重定向響應, NSURLSession 對象會調用代理方法
URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler: 代理方法會用提供的 NSRULRequest 對象(遵循重定向),或是新的 NSURLRequest 對象(如果需要重定向到一個不同的 URL 的話),或是 nil(將有效的響應作為重定向響應的 body,并將它作為結果返回) 來調用所提供的 completion handler 。
- 如果你決定追蹤重定向,返回步驟2;
- 如果代理沒有實現這個方法,這個重定向將會被重定向的最大數字跟進。
對于一個通過調用
downloadTaskWithResumeData:
或 downloadTaskWithResumeData:completionHandler: 方法創建的(重復)下載任務來說,NSURLSession 會對一個新的任務對象調用
URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes: 代理方法。對于數據任務,NSURLSession 會調用
URLSession:dataTask:didReceiveResponse:completionHandler: 代理方法,來決定是否要將 數據任務轉換成下載任務,然后調用完成回調來繼續接收或下載數據。 如果你的 app 選擇將數據任務轉換成下載任務,NSURLSession 會將這個新的下載任務作為一個參數來調用 URLSession:dataTask:didBecomeDownloadTask: 代理方法。調用完之后,代理不會從數據任務接收到進一步回調,而是從下載任務那里來接收回調;在服務器傳輸過程中,代理會周期性地接收到一個 task-level 的回調方法來報告傳輸的進度。 對于數據任務,session 會在接收過程中通過真正的數據塊來調用
URLSession:dataTask:didReceiveData: 代理方法。
對于下載任務來說,session 會通過已經成功寫入磁盤的 bytes 來調用
URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: 代理方法。如果用戶告訴 app 來暫停下載,通過cancelByProducingResumeData: 然后,如果用戶要求 app 繼續下載任務,將返回的恢復數據給
downloadTaskWithResumeData:
或 downloadTaskWithResumeData:completionHandler: 方法來創建一個新的下載任務,來繼續下載。(返回步驟1)對于數據任務,NSURLSession 對象可能會調用
URLSession:dataTask:willCacheResponse:completionHandler: 方法。你的 app 應該來決定是否允許緩存。如果你沒有是現在合格方法,默認會使用 session 的配置對象中的緩存方案。如果響應是由多個部分組成的編碼,session 會在
didiRecieveReponse
代理被多次調用之后再次調用didRecieveData
代理方法。如果出現這樣的情況,回調步驟8(處理didRecieveResponse
回調)如果下載任務成功完成, NSURLSession 對象會通過一個臨時文件的地址來調用任務的 URLSessionDownloadTask:didFinishDownloadingToURL: 方法。你的 app 必須從這個文件中讀取返回的數據,或是在這個代理方法 結束之前將它移到一個永久保存的地址。
任務完成時, NSURLSession 對象會通過一個錯誤對象或是
nil
(如果任務成功完成)來調用 URLSession:task:didCompleteWithError:
如果這個下載任務是可被重新喚起的,這個 NSError 對象的 userInfo 字典中會包含 key 為
NSURLSessionDownloadTaskResumeData 的值。你要將這個值傳給
downloadTaskWithResumeData:
或是 downloadTaskWithResumeData:completionHandler: 來創建一個新的下載任務以繼續這個已經存在的下載任務。
如果這個下載任務不能被重新喚起,你的 app 需要創建一個新的下載任務來從頭開始下載。
不管是哪種情況,如果傳輸因任何服務器錯誤以外的原因而失敗,返回到步驟3(創建或喚起任務對象)。
注
NSURLSession 不會通過 error 參數返回任何的服務器錯誤。通過 error 參數返回的錯誤只會是服務器端錯誤,比如不能解決 hostname,不能連接到 host。error codes 信息可在 URL Loading System Error Codes 中查詢。
服務器端錯誤會通過 ** NSHTTPURLResponse** 對象中的HTTP 狀態碼來返回,了解更多信息,請閱讀 NSHTTPURLREsponse 和 NSURLResponse 的文檔。
- 如果你不需要用到 session 了,可以通過調用 invalidateAndCancel(用來取消未完成的任務)或是 finishTasksAndInvalidate (在這個對象失效前完成未完成的任務)。 如果你不使這個session失效,它會在你的app終止時自動消失(除非它是一個含有正在進行的任務的后臺 session) session 失效后,當所有未完成的任務被取消或是完成時,session會調用 URLSession:didBecomeInvalidWithError: 方法。當代理方法返回時,session 會處置對代理的強引用。
如果你的app 取消了一個正在進行的下載任務, 當出現錯誤時,NSURLSession 對象會調用代理的 URLSession:task:didCompleteWithError: 方法。
后臺處理注意事項
因為重啟app(或是等待用戶重新喚起時)代價是相當高的,在后臺session中有些特性是不可用的。如下:
session 必須提供一個傳遞事件的代理,因為在傳輸進行中app可能會退出或是重啟,完成事件的回調 Block 是不支持的、(為了上傳和下載),這些代理在傳輸過程中表現是相同的;
只有 HTTP 和 HTTPS 協議是被支持的。其他內置的網絡協議和用戶網絡協議都不被支持;
只有上傳和下載是被支持的(沒有 data 任務);
重定向一直被允許;
全系統同時進行的后臺傳輸的數量是被限制的;
如果后臺任務未能滿足指定系統吞吐量限制,可能會被取消。也就是說,如果一個長期運行的任務在一段時間內沒有發送或者接受足夠的數據,它可能會被取消,以后再被喚起。所以,如果可能的話,讓一個傳輸可被重新喚起是很重要的。
如果后臺傳輸初始化時app是在后臺,那這個任務將被當做可裁剪的。換言之,它將被當做 配置對象的 discretionary 屬性為 true 的session中的一個任務。
譯者注:也就是說如果你要傳輸大量的數據,最好設置這個屬性為YES,這樣的話系統會延遲傳輸,直到你手機接通電源或是連上 wifi。下載大數據什么的設置為YES就對了
如果這些限制和你 app 的需求有沖突,你可以在 non-background session 中將遠程資源下載到一個文件中。這樣,當你的用戶讓你的 iOS app 進入后臺或是退出你的 OS X app 時,可以通過調用 cacelByProducingResumeData: 方法來暫停任何進行中的下載任務。當用戶重新讓 app 進入前臺時恢復下載。如果你的 app 在你獲取到任何恢復的數據之前終止了,就不能再恢復下載了。
注
后臺 session 是為了優化傳輸少量很大的資源,在必要時可以進行續傳。如果可以,你可能想要調查優化服務器端行為的方法 ,來實現這樣的用法,比如:
- 在終結點發起發送或接收 zip 或 tar格式的壓縮文件的請求,而不是分開調用多次;
- 在終結點發起發送或接收在服務器和客戶端之間的增量差異的請求;
- 在終結點發起一個可返回上傳ID的請求,這個ID可用來追蹤和恢復傳輸到服務器的數據;
- 添加一個中間的web 服務器代理請求到規范的web服務器,以方便任何上述優化。
NSCopying 行為
session 和 task 對象都遵從 NSCopying 協議,如下:
- 當你的app 拷貝一個session 或是 task 對象時,你會得到一個同樣的對象;
- 當你的app拷貝一個配置對象時,你會得到一個你可以獨立地修改的拷貝對象。
線程安全
URL Session API 自身完全是線程安全的。你可以在任何一個線程上下文中隨意創建 session 和 task,而且,當你的代理方法調用提供的 完成回調時,它的工作已自動被安排在正確的代理隊列中。
警告
你的 URLSessionDidFinishEventsForBackgroundURLSession: 代理方法可能會在第二線程中被調用。但是,在iOS中,你完成那個方法時需要在
application:handleEventsForBackgroundURLSession:CompletionHandler: app 代理方法中調用completion handler。而且你必須在主線程中調用那個 completion handler。
創建 Session
+ sessionWithConfiguration:delegate:delegateQueue:
配置 Session
configuration Property
delegate Property
delegateQueue Property
sessionDescription Property
給 Session 添加數據任務
- dataTaskWithURL:completionHandler:
- dataTaskWithRequest:completionHandler:
給 Session 添加下載任務
- downloadTaskWithURL:completionHandler:
- downloadTaskWithRequest:completionHandler:
- downloadTaskWithResumeData:completionHandler:
給 Session 添加上傳任務
- uploadTaskWithRequest:fromData:
- uploadTaskWithRequest:fromData:completionHandler:
- uploadTaskWithRequest:fromFile:
- uploadTaskWithRequest:fromFile:completionHandler:
- uploadTaskWithStreamedRequest:
給 Session添加流任務
- streamTaskWithHostName:port:- streamTaskWithNetService:
管理 Session
- getTasksWithCompletionHandler:
常量
NSURLSession-Specific NSError userInfo Dictionary Keys