鍍金池/ 教程/ iOS/ 從 NSURLConnection 到 NSURLSession
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項目介紹
Swift 的強(qiáng)大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

從 NSURLConnection 到 NSURLSession

iOS 7 和 Mac OS X 10.9 Mavericks 中一個顯著的變化就是對 Foundation URL 加載系統(tǒng)的徹底重構(gòu)。

現(xiàn)在已經(jīng)有人在深入蘋果的網(wǎng)絡(luò)層基礎(chǔ)架構(gòu)的地方做研究了,所以我想是時候來分享一些對于我對于這些新的 API 的看法和心得了,新的 API 將如何影響我們編寫程序,以及它們對于 API 設(shè)計理念的影響。

NSURLConnection 作為 Core Foundation / CFNetwork 框架的 API 之上的一個抽象,在 2003 年,隨著第一版的 Safari 的發(fā)布就發(fā)布了。NSURLConnection 這個名字,實際上是指代的 Foundation 框架的 URL 加載系統(tǒng)中一系列有關(guān)聯(lián)的組件:NSURLRequest、NSURLResponse、NSURLProtocolNSURLCache、 NSHTTPCookieStorageNSURLCredentialStorage 以及同名類 NSURLConnection。

NSURLRequest 被傳遞給 NSURLConnection。被委托對象(遵守以前的非正式協(xié)議 <NSURLConnectionDelegate><NSURLConnectionDataDelegate>)異步地返回一個 NSURLResponse 以及包含服務(wù)器返回信息的 NSData。

在一個請求被發(fā)送到服務(wù)器之前,系統(tǒng)會先查詢共享的緩存信息,然后根據(jù)策略(policy)以及可用性(availability)的不同,一個已經(jīng)被緩存的響應(yīng)可能會被立即返回。如果沒有緩存的響應(yīng)可用,則這個請求將根據(jù)我們指定的策略來緩存它的響應(yīng)以便將來的請求可以使用。

在把請求發(fā)送給服務(wù)器的過程中,服務(wù)器可能會發(fā)出鑒權(quán)查詢(authentication challenge),這可以由共享的 cookie 或機(jī)密存儲(credential storage)來自動響應(yīng),或者由被委托對象來響應(yīng)。發(fā)送中的請求也可以被注冊的 NSURLProtocol 對象所攔截,以便在必要的時候無縫地改變其加載行為。

不管怎樣,NSURLConnection 作為網(wǎng)絡(luò)基礎(chǔ)架構(gòu),已經(jīng)服務(wù)了成千上萬的 iOS 和 Mac OS 程序,并且做的還算相當(dāng)不錯。但是這些年,一些用例——尤其是在 iPhone 和 iPad 上面——已經(jīng)對 NSURLConnection 的幾個核心概念提出了挑戰(zhàn),讓蘋果有理由對它進(jìn)行重構(gòu)。

在 2013 的 WWDC 上,蘋果推出了 NSURLConnection 的繼任者:NSURLSession。


NSURLConnection 一樣,NSURLSession 指的也不僅是同名類 NSURLSession,還包括一系列相互關(guān)聯(lián)的類。NSURLSession 包括了與之前相同的組件,NSURLRequestNSURLCache,但是把 NSURLConnection 替換成了 NSURLSessionNSURLSessionConfiguration 以及 NSURLSessionTask 的 3 個子類:NSURLSessionDataTask,NSURLSessionUploadTaskNSURLSessionDownloadTask。

NSURLConnection 相比,NSURLsession 最直接的改進(jìn)就是可以配置每個 session 的緩存,協(xié)議,cookie,以及證書策略(credential policy),甚至跨程序共享這些信息。這將允許程序和網(wǎng)絡(luò)基礎(chǔ)框架之間相互獨(dú)立,不會發(fā)生干擾。每個 NSURLSession 對象都由一個 NSURLSessionConfiguration 對象來進(jìn)行初始化,后者指定了剛才提到的那些策略以及一些用來增強(qiáng)移動設(shè)備上性能的新選項。

NSURLSession 中另一大塊就是 session task。它負(fù)責(zé)處理數(shù)據(jù)的加載以及文件和數(shù)據(jù)在客戶端與服務(wù)端之間的上傳和下載。NSURLSessionTaskNSURLConnection 最大的相似之處在于它也負(fù)責(zé)數(shù)據(jù)的加載,最大的不同之處在于所有的 task 共享其創(chuàng)造者 NSURLSession 這一公共委托者(common delegate)。

我們先來深入探討 task,過后再來討論 NSURLSessionConfiguration。

NSURLSessionTask

NSURLsessionTask 是一個抽象類,其下有 3 個實體子類可以直接使用:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask。這 3 個子類封裝了現(xiàn)代程序三個最基本的網(wǎng)絡(luò)任務(wù):獲取數(shù)據(jù),比如 JSON 或者 XML,上傳文件和下載文件。

http://wiki.jikexueyuan.com/project/objc/images/5-11.png" alt="" />

當(dāng)一個 NSURLSessionDataTask 完成時,它會帶有相關(guān)聯(lián)的數(shù)據(jù),而一個 NSURLSessionDownloadTask 任務(wù)結(jié)束時,它會帶回已下載文件的一個臨時的文件路徑。因為一般來說,服務(wù)端對于一個上傳任務(wù)的響應(yīng)也會有相關(guān)數(shù)據(jù)返回,所以 NSURLSessionUploadTask 繼承自 NSURLSessionDataTask。

所有的 task 都是可以取消,暫停或者恢復(fù)的。當(dāng)一個 download task 取消時,可以通過選項來創(chuàng)建一個恢復(fù)數(shù)據(jù)(resume data),然后可以傳遞給下一次新創(chuàng)建的 download task,以便繼續(xù)之前的下載。

不同于直接使用 alloc-init 初始化方法,task 是由一個 NSURLSession 創(chuàng)建的。每個 task 的構(gòu)造方法都對應(yīng)有或者沒有 completionHandler 這個 block 的兩個版本,例如:有這樣兩個構(gòu)造方法 –dataTaskWithRequest:–dataTaskWithRequest:completionHandler:。這與 NSURLConnection-sendAsynchronousRequest:queue:completionHandler: 方法類似,通過指定 completionHandler 這個 block 將創(chuàng)建一個隱式的 delegate,來替代該 task 原來的 delegate——session。對于需要 override 原有 session task 的 delegate 的默認(rèn)行為的情況,我們需要使用這種不帶 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 的構(gòu)造方法上延續(xù)了這一模式。不同的是,這里不會立即運(yùn)行 task,而是將該 task 對象先返回,允許我們進(jìn)一步的配置,然后可以使用 resume 方法來讓它開始運(yùn)行。

Data task 可以通過 NSURLNSURLRequest 創(chuàng)建(使用前者相當(dāng)于是使用一個對于該 URL 進(jìn)行標(biāo)準(zhǔn) 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 對象或者是一個本地文件的路徑對應(yīng)的 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 會在任務(wù)完成時一次性返回,但是 Download task 是將數(shù)據(jù)一點(diǎn)點(diǎn)地寫入本地的臨時文件。所以在 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,本文已進(jìn)行更正,如果您有不同意見,歡迎在 Github 上給我們反饋。

NSURLSession 與 NSURLConnection 的 delegate 方法

總體而言,NSURLSession 的 delegate 方法是 NSURLConnection 的演化的十年中對于 ad-hoc 模式的一個顯著改善。您可以查看這個映射表來進(jìn)行一個完整的概覽。

以下是一些具體的觀察:

NSURLSession 既擁有 seesion 的 delegate 方法,又擁有 task 的 delegate 方法用來處理鑒權(quán)查詢。session 的 delegate 方法處理連接層的問題,諸如服務(wù)器信任,客戶端證書的評估,NTLMKerberos 協(xié)議這類問題,而 task 的 delegate 則處理以網(wǎng)絡(luò)請求為基礎(chǔ)的問題,如 Basic,Digest,以及代理身份驗證(Proxy authentication)等。

NSURLConnection 中有兩個 delegate 方法可以表明一個網(wǎng)絡(luò)請求已經(jīng)結(jié)束:NSURLConnectionDataDelegate 中的 -connectionDidFinishLoading:NSURLConnectionDelegate 中的 -connection:didFailWithError:,而在 NSURLSession 中改為一個 delegate 方法:NSURLSessionTaskDelegate-URLSession:task:didCompleteWithError:

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

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

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

NSURLSessionConfiguration

NSURLSessionConfiguration 對象用于對 NSURLSession 對象進(jìn)行初始化。NSURLSessionConfiguration 對以前 NSMutableURLRequest 所提供的網(wǎng)絡(luò)請求層的設(shè)置選項進(jìn)行了擴(kuò)充,提供給我們相當(dāng)大的靈活性和控制權(quán)。從指定可用網(wǎng)絡(luò),到 cookie,安全性,緩存策略,再到使用自定義協(xié)議,啟動事件的設(shè)置,以及用于移動設(shè)備優(yōu)化的幾個新屬性,你會發(fā)現(xiàn)使用 NSURLSessionConfiguration 可以找到幾乎任何你想要進(jìn)行配置的選項。

NSURLSession 在初始化時會把配置它的 NSURLSessionConfiguration 對象進(jìn)行一次 copy,并保存到自己的 configuration 屬性中,而且這個屬性是只讀的。因此之后再修改最初配置 session 的那個 configuration 對象對于 session 是沒有影響的。也就是說,configuration 只在初始化時被讀取一次,之后都是不會變化的。

NSURLSessionConfiguration 的工廠方法

NSURLSessionConfiguration 有三個類工廠方法,這很好地說明了 NSURLSession 設(shè)計時所考慮的不同的使用場景。

+defaultSessionConfiguration 返回一個標(biāo)準(zhǔn)的 configuration,這個配置實際上與 NSURLConnection網(wǎng)絡(luò)堆棧(networking stack)是一樣的,具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享 NSURLCredentialStorage。

+ephemeralSessionConfiguration 返回一個預(yù)設(shè)配置,這個配置中不會對緩存,Cookie 和證書進(jìn)行持久性的存儲。這對于實現(xiàn)像秘密瀏覽這種功能來說是很理想的。

+backgroundSessionConfiguration:(NSString *)identifier 的獨(dú)特之處在于,它會創(chuàng)建一個后臺 session。后臺 session 不同于常規(guī)的,普通的 session,它甚至可以在應(yīng)用程序掛起,退出或者崩潰的情況下運(yùn)行上傳和下載任務(wù)。初始化時指定的標(biāo)識符,被用于向任何可能在進(jìn)程外恢復(fù)后臺傳輸?shù)?strong>守護(hù)進(jìn)程(daemon)提供上下文。

想要查看更多關(guān)于后臺 session 的信息,可以查看 WWDC Session 204: "What's New with Multitasking"

配置屬性

NSURLSessionConfiguration 擁有 20 個配置屬性。熟練掌握這些配置屬性的用處,可以讓應(yīng)用程序充分地利用其網(wǎng)絡(luò)環(huán)境。

基本配置

HTTPAdditionalHeaders 指定了一組默認(rèn)的可以設(shè)置出站請求(outbound request)的數(shù)據(jù)頭。這對于跨 session 共享信息,如內(nèi)容類型,語言,用戶代理和身份認(rèn)證,是很有用的。

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 對標(biāo)準(zhǔn)的網(wǎng)絡(luò)流量,網(wǎng)絡(luò)電話,語音,視頻,以及由一個后臺進(jìn)程使用的流量進(jìn)行了區(qū)分。大多數(shù)應(yīng)用程序都不需要設(shè)置這個。

allowsCellularAccessdiscretionary 被用于節(jié)省通過蜂窩網(wǎng)絡(luò)連接的帶寬。對于后臺傳輸?shù)那闆r,推薦大家使用 discretionary 這個屬性,而不是 allowsCellularAccess,因為前者會把 WiFi 和電源的可用性考慮在內(nèi)。

timeoutIntervalForRequesttimeoutIntervalForResource 分別指定了對于請求和資源的超時間隔。許多開發(fā)人員試圖使用 timeoutInterval 去限制發(fā)送請求的總時間,但其實它真正的含義是:分組(packet)之間的時間。實際上我們應(yīng)該使用 timeoutIntervalForResource 來規(guī)定整體超時的總時間,但應(yīng)該只將其用于后臺傳輸,而不是用戶實際上可能想要去等待的任何東西。

HTTPMaximumConnectionsPerHost 是 Foundation 框架中 URL 加載系統(tǒng)的一個新的配置選項。它曾經(jīng)被 NSURLConnection 用于管理私有的連接池?,F(xiàn)在有了 NSURLSession,開發(fā)者可以在需要時限制連接到特定主機(jī)的數(shù)量。

HTTPShouldUsePipelining 這個屬性在 NSMutableURLRequest 下也有,它可以被用于開啟 HTTP 管線化(HTTP pipelining,這可以顯著降低請求的加載時間,但是由于沒有被服務(wù)器廣泛支持,默認(rèn)是禁用的。

sessionSendsLaunchEvents 是另一個新的屬性,該屬性指定該 session 是否應(yīng)該從后臺啟動。

connectionProxyDictionary 指定了 session 連接中的代理服務(wù)器。同樣地,大多數(shù)面向消費(fèi)者的應(yīng)用程序都不需要代理,所以基本上不需要配置這個屬性。

關(guān)于連接代理的更多信息可以在 CFProxySupport Reference 找到。

Cookie 策略

HTTPCookieStorage 存儲了 session 所使用的 cookie。默認(rèn)情況下會使用 NSHTTPCookieShorage+sharedHTTPCookieStorage 這個單例對象,這與 NSURLConnection 是相同的。

HTTPCookieAcceptPolicy 決定了什么情況下 session 應(yīng)該接受從服務(wù)器發(fā)出的 cookie。

HTTPShouldSetCookies 指定了請求是否應(yīng)該使用 session 存儲的 cookie,即 HTTPCookieSorage 屬性的值。

安全策略

URLCredentialStorage 存儲了 session 所使用的證書。默認(rèn)情況下會使用 NSURLCredentialStorage+sharedCredentialStorage 這個單例對象,這與 NSURLConnection 是相同的。

TLSMaximumSupportedProtocolTLSMinimumSupportedProtocol 確定 session 是否支持 SSL 協(xié)議。

緩存策略

URLCache 是 session 使用的緩存。默認(rèn)情況下會使用 NSURLCache+sharedURLCache 這個單例對象,這與 NSURLConnection 是相同的。

requestCachePolicy specifies when a cached response should be returned for a request. This is equivalent to NSURLRequest -cachePolicy.

requestCachePolicy 指定了一個請求的緩存響應(yīng)應(yīng)該在什么時候返回。這相當(dāng)于 NSURLRequest-cachePolicy 方法。

自定義協(xié)議

protocolClasses 用來配置特定某個 session 所使用的自定義協(xié)議(該協(xié)議是 NSURLProtocol 的子類)的數(shù)組。

結(jié)論

iOS 7 和 Mac OS X 10.9 Mavericks 中 URL 加載系統(tǒng)的變化,是對 NSURLConnection 進(jìn)行深思熟慮后的一個自然而然的進(jìn)化??傮w而言,蘋果的 Foundation 框架團(tuán)隊干了一件令人欽佩的的工作,他們研究并預(yù)測了移動開發(fā)者現(xiàn)有的和新興的用例,創(chuàng)造了能夠滿足日常任務(wù)而且非常好用的 API 。

盡管在這個體系結(jié)構(gòu)中,某些決定對于可組合性和可擴(kuò)展性而言是一種倒退,但是 NSURLSession 仍然是實現(xiàn)更高級別網(wǎng)絡(luò)功能的一個強(qiáng)大的基礎(chǔ)框架。