深入瞭解 NSURLSession

Jerry4me發表於2017-03-28

前言

本文主要是結合官方文件, 挖掘NSURLSession的類層次結構及其聯絡, 總結出關於NSURLSession的一些關鍵點及其用法.

關於NSURLSession為什麼能取代NSURLConnection, 其優勢是什麼, 及其NSURLSession API的概述, 見
關於ATS, HTTP/2, 以及iOS9 NSURLSession新特性 : sharedCookies, streamTask和taskMetrics, 見

以上兩篇文章都是我看wwdc視訊然後總結出來的文章, 大家感興趣的可以先了解了解. 如果不想知道那麼多, 只想知道怎麼用NSURLSession, 那就直接看本文的正文.

*瞭解URL Loading System

深入瞭解 NSURLSession

目錄



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

NSURLSession

session分為 :

  • 全域性共享單例session : NSURLSession sharedSession, 有一定的侷限性
  • 自定義session : 自定義配置檔案, 設定代理, 大部分時間我們都是用這個
  • 後臺session : 也是自定義session的一種, 只是他專門用於做後臺上傳/下載任務

session為哪一種型別完全由其內部的Configuration而定.

NSURLSessionConfiguration

配置分為 :

  • defaultSessionConfiguration : 系統預設
  • ephemeralSessionConfiguration : 僅記憶體快取, 不做磁碟快取的配置
  • backgroundSessionConfiguration : 這裡需要指定一個identifier, identifier用來後臺重連session物件. (做後臺上傳/下載就是這個config)

另外, 我們還可以給Configuration物件再自定義一些屬性, 例如每埠的最大併發HTTP請求數目, 以及是否允許蜂窩網路, 請求快取策略, 請求超時, cookies/證書儲存策略等等

NSURLSessionDelegate

深入瞭解 NSURLSession

session管理的一組tasks共享一個代理, 不想實現代理方法時, 代理傳nil即可.
代理協議分為 :

  • NSURLSessionDelegate : session-level的代理方法
  • NSURLSessionTaskDelegate : task-level面向all的代理方法
  • NSURLSessionDataDelegate : task-level面向data和upload的代理方法
  • NSURLSessionDownloadDelegate : task-level的面向download的代理方法
  • NSURLSessionStreamDelegate : task-level的面向stream的代理方法
NSURLSessionTask

深入瞭解 NSURLSession

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物件的陣列. 每個物件都代表下圖的一個子過程

深入瞭解 NSURLSession

API很簡單, 就一個方法 : - (void)URLSession: task: didFinishCollectingMetrics:, 當收集完成的時候就會呼叫該方法.

身份驗證和自定義TLS


  1. 當一個伺服器請求身份驗證或TLS握手期間需要提供證書的話, URLSession會呼叫他的代理方法URLSession:didReceiveChallenge:completionHandler:去處理.
  2. 如果你沒有實現該代理方法, URLSession就會這麼做 :
    • 使用身份認證資訊作為請求URL的一部分(如果可用的話)
    • 在使用者的keychain中查詢網路密碼和證書(in macOS), 在app的keychain中查詢(in iOS)
  3. 如果證書還是不可用或伺服器拒絕該證書, 就會繼續缺少身份認證的連線.
    • 對於HTTP(S)連線, 請求失敗並返回一個狀態碼, 可能會提供一些替代的內容, 例如一個私人網站的公共網頁.
    • 對於其他URL型別(如FTP等), 則連線請求失敗, 直接返回錯誤資訊

App Transport Security


從iOS9開始支援ATS, 且預設ATS只支援傳送HTTPS請求, 不允許傳送不安全的HTTP請求. 如果使用者需要傳送HTTP請求需要在info.plist中配置一些東西.

詳情在文章開頭的iOS9 ATS HTTP/2 NSURLSession中說得很詳細, 想了解的可以進去閱讀.

NSURLSession 工作流程


那麼如何使用NSURLSession像從前用NSURLConnection那樣傳送一個請求呢?

然後就可以在代理方法中處理各種事情了. 簡單吧? 下面分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

  1. 對於一個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的回撥
  2. 在伺服器傳輸資料給客戶端期間, 代理會週期性地收到URLSession:dataTask:didReceiveData:回撥
  3. session會呼叫URLSession:dataTask:willCacheResponse:completionHandler:詢問你的app是否允許快取. 如果代理不實現這個方法的話, 預設使用session繫結的Configuration的快取策略.

DownloadTask

  1. 對於一個通過downloadTaskWithResumeData:建立的下載任務, session會呼叫代理的URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:方法.
  2. 在伺服器傳輸資料給客戶端期間, 呼叫URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:給使用者傳資料
    • 當使用者暫停下載時, 呼叫cancelByProducingResumeData:給使用者傳已下好的資料.
    • 如果使用者想要恢復下載, 把剛剛的resumeData以引數的形式傳給downloadTaskWithResumeData:方法建立新的task繼續下載.
  3. 如果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了, 一定要呼叫它的invalidateAndCancelfinishTasksAndInvalidate方法. (前者是取消所有未完成的任務然後使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
  • 建立一個upload or download task
  • 我們等下載到一半後進入後臺, 開啟App Switcher過一會可以發現, 圖片下載完之後就會顯示在應用程式上. 方法呼叫順序為 : 下面四個方法全部都是app在後臺時呼叫的

深入瞭解 NSURLSession

總結後臺傳輸

  1. 儘量用真機進行除錯, 模擬器會跳過某一兩個方法
  2. 只能進行upload/download task, 不能進行data task
  3. 不能使用帶completionHandler的方法建立task, 否則程式直接掛掉
  4. Applecation裡的completionHandler必須儲存起來, 等你處理完所有事情之後再呼叫告訴系統可以進行Snapshot和掛起app了
  5. 後臺下載最好支援斷點續傳, 因為任務有可能會被系統主動取消(例如系統效能下降了, 資源不夠用的情況下)

後臺傳輸的Demo在文章頭部的地方, 也可以點這裡進去

NSURLSession API


  • 建立Session

    • + sessionWithConfiguration: : 建立一個指定配置的session
    • + sessionWithConfiguration:delegate:delegateQueue: : 建立一個指定配置, 代理和代理方法執行佇列的session
    • sharedSession : 返回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 : 任務全部完成後銷燬session
    • flushWithCompletionHandler: : 清除硬碟上的cookies和證書, 清理暫時的快取, 確保未來能響應一個新的TCP請求
    • getTasksWithCompletionHandler: : 非同步呼叫session中所有upload, download, data tasks的completion回撥.
    • invalidateAndCancel : 取消所有未完成的任務並銷燬session
    • resetWithCompletionHandler: : 清空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的keys
  • Background 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

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

深入瞭解 NSURLSession 深入瞭解 NSURLSession

相關文章