從 NSURLConnection 到 NSURLSession

轉載自https://objccn.io/issue-5-4/
從 NSURLConnection 到 NSURLSession
方一雄
2014/04/11

iOS 7 和 Mac OS X 10.9 Mavericks 中一個顯著的變化就是對 Foundation URL 加載系統(tǒng)的徹底重構。
現在已經有人在深入蘋果的網絡層基礎架構的地方做研究了,所以我想是時候來分享一些對于我對于這些新的 API 的看法和心得了,新的 API 將如何影響我們編寫程序,以及它們對于 API 設計理念的影響。
NSURLConnection
作為 Core Foundation / CFNetwork 框架的 API 之上的一個抽象,在 2003 年,隨著第一版的 Safari 的發(fā)布就發(fā)布了。NSURLConnection
這個名字,實際上是指代的 Foundation 框架的 URL 加載系統(tǒng)中一系列有關聯的組件:NSURLRequest
、NSURLResponse
、NSURLProtocol
、NSURLCache
、NSHTTPCookieStorage
、NSURLCredentialStorage
以及同名類NSURLConnection
。
NSURLRequest
被傳遞給NSURLConnection
。被委托對象(遵守以前的非正式協(xié)議<NSURLConnectionDelegate>
和<NSURLConnectionDataDelegate>
)異步地返回一個NSURLResponse
以及包含服務器返回信息的NSData
。
在一個請求被發(fā)送到服務器之前,系統(tǒng)會先查詢共享的緩存信息,然后根據策略(policy)以及可用性(availability)的不同,一個已經被緩存的響應可能會被立即返回。如果沒有緩存的響應可用,則這個請求將根據我們指定的策略來緩存它的響應以便將來的請求可以使用。
在把請求發(fā)送給服務器的過程中,服務器可能會發(fā)出鑒權查詢(authentication challenge),這可以由共享的 cookie 或機密存儲(credential storage)來自動響應,或者由被委托對象來響應。發(fā)送中的請求也可以被注冊的NSURLProtocol
對象所攔截,以便在必要的時候無縫地改變其加載行為。
不管怎樣,NSURLConnection
作為網絡基礎架構,已經服務了成千上萬的 iOS 和 Mac OS 程序,并且做的還算相當不錯。但是這些年,一些用例——尤其是在 iPhone 和 iPad 上面——已經對NSURLConnection
的幾個核心概念提出了挑戰(zhàn),讓蘋果有理由對它進行重構。
在 2013 的 WWDC 上,蘋果推出了NSURLConnection
的繼任者:NSURLSession
。
和NSURLConnection
一樣,NSURLSession
指的也不僅是同名類NSURLSession
,還包括一系列相互關聯的類。NSURLSession
包括了與之前相同的組件,NSURLRequest
與NSURLCache
,但是把NSURLConnection
替換成了NSURLSession
、NSURLSessionConfiguration
以及NSURLSessionTask
的 3 個子類:NSURLSessionDataTask
,NSURLSessionUploadTask
,NSURLSessionDownloadTask
。
與NSURLConnection
相比,NSURLsession
最直接的改進就是可以配置每個 session 的緩存,協(xié)議,cookie,以及證書策略(credential policy),甚至跨程序共享這些信息。這將允許程序和網絡基礎框架之間相互獨立,不會發(fā)生干擾。每個NSURLSession
對象都由一個NSURLSessionConfiguration
對象來進行初始化,后者指定了剛才提到的那些策略以及一些用來增強移動設備上性能的新選項。
NSURLSession
中另一大塊就是 session task。它負責處理數據的加載以及文件和數據在客戶端與服務端之間的上傳和下載。NSURLSessionTask
與NSURLConnection
最大的相似之處在于它也負責數據的加載,最大的不同之處在于所有的 task 共享其創(chuàng)造者NSURLSession
這一公共委托者(common delegate)。
我們先來深入探討 task,過后再來討論NSURLSessionConfiguration
。
NSURLSessionTask
NSURLsessionTask
是一個抽象類,其下有 3 個實體子類可以直接使用:NSURLSessionDataTask
、NSURLSessionUploadTask
、NSURLSessionDownloadTask
。這 3 個子類封裝了現代程序三個最基本的網絡任務:獲取數據,比如 JSON 或者 XML,上傳文件和下載文件。

NSURLSessionTask class diagram
NSURLSessionTask class diagram

當一個NSURLSessionDataTask
完成時,它會帶有相關聯的數據,而一個NSURLSessionDownloadTask
任務結束時,它會帶回已下載文件的一個臨時的文件路徑。因為一般來說,服務端對于一個上傳任務的響應也會有相關數據返回,所以NSURLSessionUploadTask
繼承自NSURLSessionDataTask
。
所有的 task 都是可以取消,暫?;蛘呋謴偷摹.斠粋€ download task 取消時,可以通過選項來創(chuàng)建一個恢復數據(resume data),然后可以傳遞給下一次新創(chuàng)建的 download task,以便繼續(xù)之前的下載。
不同于直接使用alloc-init
初始化方法,task 是由一個NSURLSession
創(chuàng)建的。每個 task 的構造方法都對應有或者沒有completionHandler
這個 block 的兩個版本,例如:有這樣兩個構造方法–dataTaskWithRequest:
和–dataTaskWithRequest:completionHandler:
。這與NSURLConnection
的-sendAsynchronousRequest:queue:completionHandler:
方法類似,通過指定completionHandler
這個 block 將創(chuàng)建一個隱式的 delegate,來替代該 task 原來的 delegate——session。對于需要 override 原有 session task 的 delegate 的默認行為的情況,我們需要使用這種不帶completionHandler
的版本。
NSURLSessionTask 的工廠方法
在 iOS 5 中,NSURLConnection
添加了sendAsynchronousRequest:queue:completionHandler:
這一方法,對于一次性使用的 request, 大大地簡化代碼,同時它也是sendSynchronousRequest:returningResponse:error:
這個方法的異步替代品:

NSURL *URL = [NSURL URLWithString:@"http://example.com"]; 
NSURLRequest *request = [NSURLRequest requestWithURL:URL]; 
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { // ... }];

NSURLSession
在 task 的構造方法上延續(xù)了這一模式。不同的是,這里不會立即運行 task,而是將該 task 對象先返回,允許我們進一步的配置,然后可以使用resume
方法來讓它開始運行。
Data task 可以通過NSURL
或NSURLRequest
創(chuàng)建(使用前者相當于是使用一個對于該 URL 進行標準GET
請求的NSURLRequest
,這是一種快捷方法):

NSURL *URL = [NSURL URLWithString:@"http://example.com"]; 
NSURLRequest *request = [NSURLRequest requestWithURL:URL]; 
NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { // ... }]; [task resume];

Upload task 的創(chuàng)建需要使用一個 request,另外加上一個要上傳的NSData
對象或者是一個本地文件的路徑對應的NSURL

NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL]; 
NSData *data = ...;
NSURLSession *session = [NSURLSession sharedSession]; 
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { // ... }]; [uploadTask resume];

Download task 也需要一個 request,不同之處在于completionHandler
這個 block。Data task 和 upload task 會在任務完成時一次性返回,但是 Download task 是將數據一點點地寫入本地的臨時文件。所以在completionHandler
這個 block 里,我們需要把文件從一個臨時地址移動到一個永久的地址保存起來:

NSURL *URL = [NSURL URLWithString:@"http://example.com/file.zip"]; 
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession]; 
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler: ^(NSURL *location, NSURLResponse *response, NSError *error) { NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; 
NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:documentsPath]; 
NSURL *newFileLocation = [documentsDirectoryURL URLByAppendingPathComponent:[[response URL] lastPathComponent]]; 
[[NSFileManager defaultManager] copyItemAtURL:location toURL:newFileLocation error:nil]; }];
 [downloadTask resume];

編者注 原文中這塊代碼以及上文的表述中存有一些問題,詳見這個 issue,本文已進行更正,如果您有不同意見,歡迎在 Github 上給我們反饋。

NSURLSession 與 NSURLConnection 的 delegate 方法
總體而言,NSURLSession
的 delegate 方法是NSURLConnection
的演化的十年中對于 ad-hoc 模式的一個顯著改善。您可以查看這個映射表來進行一個完整的概覽。
以下是一些具體的觀察:
NSURLSession
既擁有 seesion 的 delegate 方法,又擁有 task 的 delegate 方法用來處理鑒權查詢。session 的 delegate 方法處理連接層的問題,諸如服務器信任,客戶端證書的評估,NTLM 和 Kerberos 協(xié)議這類問題,而 task 的 delegate 則處理以網絡請求為基礎的問題,如 Basic,Digest,以及代理身份驗證(Proxy authentication)等。
在NSURLConnection
中有兩個 delegate 方法可以表明一個網絡請求已經結束:NSURLConnectionDataDelegate
中的-connectionDidFinishLoading:
和NSURLConnectionDelegate
中的-connection:didFailWithError:
,而在NSURLSession
中改為一個 delegate 方法:NSURLSessionTaskDelegate
的-URLSession:task:didCompleteWithError:

NSURLSession
中表示傳輸多少字節(jié)的參數類型現在改為int64_t
,以前在NSURLConnection
中相應的參數的類型是long long

由于增加了completionHandler:
這個 block 作為參數,NSURLSession
實際上給 Foundation 框架引入了一種全新的模式。這種模式允許 delegate 方法可以安全地在主線程與運行,而不會阻塞主線程;Delgate 只需要簡單地調用dispatch_async
就可以切換到后臺進行相關的操作,然后在操作完成時調用completionHandler
即可。同時,它還可以有效地擁有多個返回值,而不需要我們使用笨拙的參數指針。以NSURLSessionTaskDelegate
的-URLSession:task:didReceiveChallenge:completionHandler:
方法來舉例,completionHandler
接受兩個參數:NSURLSessionAuthChallengeDisposition
和NSURLCredential
,前者為應對鑒權查詢的策略,后者為需要使用的證書(僅當前者——應對鑒權查詢的策略為使用證書,即NSURLSessionAuthChallengeUseCredential
時有效,否則該參數為NULL

想要查看更多關于 session task 的信息,可以查看 WWDC Session 705: "What’s New in Foundation Networking"

NSURLSessionConfiguration
NSURLSessionConfiguration
對象用于對NSURLSession
對象進行初始化。NSURLSessionConfiguration
對以前NSMutableURLRequest
所提供的網絡請求層的設置選項進行了擴充,提供給我們相當大的靈活性和控制權。從指定可用網絡,到 cookie,安全性,緩存策略,再到使用自定義協(xié)議,啟動事件的設置,以及用于移動設備優(yōu)化的幾個新屬性,你會發(fā)現使用NSURLSessionConfiguration
可以找到幾乎任何你想要進行配置的選項。
NSURLSession
在初始化時會把配置它的NSURLSessionConfiguration
對象進行一次 copy,并保存到自己的configuration
屬性中,而且這個屬性是只讀的。因此之后再修改最初配置 session 的那個 configuration 對象對于 session 是沒有影響的。也就是說,configuration 只在初始化時被讀取一次,之后都是不會變化的。
NSURLSessionConfiguration 的工廠方法
NSURLSessionConfiguration
有三個類工廠方法,這很好地說明了NSURLSession
設計時所考慮的不同的使用場景。
+defaultSessionConfiguration
返回一個標準的 configuration,這個配置實際上與NSURLConnection
網絡堆棧(networking stack)是一樣的,具有相同的共享NSHTTPCookieStorage
,共享NSURLCache
和共享NSURLCredentialStorage

+ephemeralSessionConfiguration
返回一個預設配置,這個配置中不會對緩存,Cookie 和證書進行持久性的存儲。這對于實現像秘密瀏覽這種功能來說是很理想的。
+backgroundSessionConfiguration:(NSString )identifier
的獨特之處在于,它會創(chuàng)建一個
后臺 session。后臺 session 不同于常規(guī)的,普通的 session,它甚至可以在應用程序掛起,退出或者崩潰的情況下運行上傳和下載任務。初始化時指定的標識符,被用于向任何可能在進程外恢復后臺傳輸的守護進程(daemon)提供上下文。
想要查看更多關于后臺 session 的信息,可以查看 WWDC Session 204: "What's New with Multitasking"
配置屬性
NSURLSessionConfiguration
擁有 20 個配置屬性。熟練掌握這些配置屬性的用處,可以讓應用程序充分地利用其網絡環(huán)境。
基本配置
HTTPAdditionalHeaders
指定了一組默認的可以設置
出站請求(outbound request)*的數據頭。這對于跨 session 共享信息,如內容類型,語言,用戶代理和身份認證,是很有用的。

NSString *userPasswordString = [NSString stringWithFormat:@"%@:%@", user, password];NSData * userPasswordData = [userPasswordString dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
NSString *authString = [NSString stringWithFormat:@"Basic %@", base64EncodedCredential];
NSString *userAgentString = @"AppName/com.example.app (iPhone 5s; iOS 7.0.2; Scale/2.0)";configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json", @"Accept-Language": @"en", @"Authorization": authString, @"User-Agent": userAgentString};

networkServiceType
對標準的網絡流量,網絡電話,語音,視頻,以及由一個后臺進程使用的流量進行了區(qū)分。大多數應用程序都不需要設置這個。
allowsCellularAccess
和discretionary
被用于節(jié)省通過蜂窩網絡連接的帶寬。對于后臺傳輸的情況,推薦大家使用discretionary
這個屬性,而不是allowsCellularAccess
,因為前者會把 WiFi 和電源的可用性考慮在內。
timeoutIntervalForRequest
和timeoutIntervalForResource
分別指定了對于請求和資源的超時間隔。許多開發(fā)人員試圖使用timeoutInterval
去限制發(fā)送請求的總時間,但其實它真正的含義是:分組(packet)之間的時間。實際上我們應該使用timeoutIntervalForResource
來規(guī)定整體超時的總時間,但應該只將其用于后臺傳輸,而不是用戶實際上可能想要去等待的任何東西。
HTTPMaximumConnectionsPerHost
是 Foundation 框架中 URL 加載系統(tǒng)的一個新的配置選項。它曾經被NSURLConnection
用于管理私有的連接池。現在有了NSURLSession
,開發(fā)者可以在需要時限制連接到特定主機的數量。
HTTPShouldUsePipelining
這個屬性在NSMutableURLRequest
下也有,它可以被用于開啟 HTTP 管線化(HTTP pipelining),這可以顯著降低請求的加載時間,但是由于沒有被服務器廣泛支持,默認是禁用的。
sessionSendsLaunchEvents
是另一個新的屬性,該屬性指定該 session 是否應該從后臺啟動。
connectionProxyDictionary
指定了 session 連接中的代理服務器。同樣地,大多數面向消費者的應用程序都不需要代理,所以基本上不需要配置這個屬性。
關于連接代理的更多信息可以在 CFProxySupport
Reference 找到。

Cookie 策略
HTTPCookieStorage
存儲了 session 所使用的 cookie。默認情況下會使用NSHTTPCookieShorage
的+sharedHTTPCookieStorage
這個單例對象,這與NSURLConnection
是相同的。
HTTPCookieAcceptPolicy
決定了什么情況下 session 應該接受從服務器發(fā)出的 cookie。
HTTPShouldSetCookies
指定了請求是否應該使用 session 存儲的 cookie,即HTTPCookieSorage
屬性的值。
安全策略
URLCredentialStorage
存儲了 session 所使用的證書。默認情況下會使用NSURLCredentialStorage
的+sharedCredentialStorage
這個單例對象,這與NSURLConnection
是相同的。
TLSMaximumSupportedProtocol
和TLSMinimumSupportedProtocol
確定 session 是否支持 SSL 協(xié)議。
緩存策略
URLCache
是 session 使用的緩存。默認情況下會使用NSURLCache
的+sharedURLCache
這個單例對象,這與NSURLConnection
是相同的。
requestCachePolicy
指定了一個請求的緩存響應應該在什么時候返回。這相當于NSURLRequest
的-cachePolicy
方法。
自定義協(xié)議
protocolClasses
用來配置特定某個 session 所使用的自定義協(xié)議(該協(xié)議是NSURLProtocol
的子類)的數組。
結論
iOS 7 和 Mac OS X 10.9 Mavericks 中 URL 加載系統(tǒng)的變化,是對NSURLConnection
進行深思熟慮后的一個自然而然的進化??傮w而言,蘋果的 Foundation 框架團隊干了一件令人欽佩的的工作,他們研究并預測了移動開發(fā)者現有的和新興的用例,創(chuàng)造了能夠滿足日常任務而且非常好用的 API 。
盡管在這個體系結構中,某些決定對于可組合性和可擴展性而言是一種倒退,但是NSURLSession
仍然是實現更高級別網絡功能的一個強大的基礎框架。
原文 From NSURLConnection to NSURLSession

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • iOS 7 和 Mac OS X 10.9 Mavericks 中一個顯著的變化就是對 Foundation UR...
    kobehjk閱讀 286評論 0 2
  • 半夜睡不著,寫一點美好的事物。 一、城市篇(在世界化高速發(fā)展的今天,城市還是各有不同。) 1.曼谷 (一個充滿...
    汐曼閱讀 565評論 0 0
  • 漫天的灰燼與火星在我身邊飄蕩 我感覺我要窒息了,快昏迷的那種窒息 站不起來,睜不開眼,說不出話,默默忍受 這溫水要...
    白森丶閱讀 143評論 0 0
  • 【原文】子曰:“君子無所爭,必也射乎!揖讓而升,下而飲,其爭也君子?!?【譯文】孔子說:“君子沒有什么與人相爭的事...
    轉念館閱讀 1,606評論 0 0
  • 11月27日,禮拜一,再過一周就十二月,十二月結束,2018年就開始啦?。。?! ⒈曾經開玩笑,像我,不喜歡美食!不...
    李豫一閱讀 236評論 0 0

友情鏈接更多精彩內容