天天看點

iOS 之NSURLConnection和NSURLSession

說到 iOS 7 和 Mac OS X 10.9 Mavericks 的顯著變化,其中一個就是Foundation架構中URL加載系統的優化。

  此時可能有人正沉浸在Apple的網絡基礎架構,我想在這裡分享一下我對這些新APIs的看法,并展示這些新APIs如何改變我們建構應用程式的方式,以及這些它們在API設計理念演變中的意義。

  作為Core Foundation / CFNetwork 架構的APIs之上的一個抽象,NSURLConnection伴随着2003年Safari浏覽器的原始發行版本,誕生于10年前。NSURLConnection這個名字,實際上指的是一組構成Foundation架構中URL加載系統的互相關聯的元件:NSURLRequest,NSURLResponse,NSURLProtocol,NSURLCache,NSHTTPCookieStorage,NSURLCredentialStorage,以及和它同名的NSURLConnection。

  NSURLRequest對象被傳遞給一個NSURLConnection對象。委托(遵守從前的非正式<NSURLConnectionDelegate> 和 <NSURLConnectionDataDelegate>協定)作為一個NSURLResponse異步響應,任何相關的NSData從伺服器發送。

  一個請求發送到伺服器前,共享的高速緩存先被通路,然後根據政策(policy)和可用性(availability),一個緩存的響應可能立即透明地傳回,如果所有緩存的響應都不可用,則該請求根據選項,被用于為任何後續請求緩存它的響應。

  在協商發送一個請求到伺服器的過程中,該伺服器可發出驗證質詢,這可以由共享的cookie,證書存儲(credential storage)或通過連接配接委托自動處理。必要的時候,為了無縫地改變裝載行為,傳出請求也可以被注冊的NSURLProtocol對象截獲。

  不管怎樣,考慮到NSURLConnection作為一個網絡基礎架構,成千上萬的Cocoa和Cocoa Touch應用程式從中獲益,它已經表現得相當好。但是,這些年來,iPhone和iPad新興的用例,特别是有一些已經向NSURLConnection的幾個核心設想提出了挑戰,對其重構已經迫在眉睫。

  在2013年的WWDC上,Apple揭開了NSURLConnection繼任者的面紗:NSURLSession。

  

iOS 之NSURLConnection和NSURLSession

  與NSURLConnection類似,除了同名類NSURLSession,NSURLSession也是指一組互相依賴的類。NSURLSession包括與之前相同的元件,例如NSURLRequest, NSURLCache等。NSURLSession的不同之處在于,它把 NSURLConnection替換為NSURLSession, NSURLSessionConfiguration,以及3個NSURLSessionTask的子類:NSURLSessionDataTask, NSURLSessionUploadTask, 和NSURLSessionDownloadTask.

  與NSURLConnection相比,NSURLSession最直接的改善就是提供了配置每個會話的緩存,協定,cookie和證書政策(credential policies),甚至跨應用程式共享它們的能力。這使得架構的網絡基礎架構和部分應用程式獨立工作,而不會互相幹擾。每一個NSURLSession對象都是根據一個NSURLSessionConfiguration初始化的,該NSURLSessionConfiguration指定了上面提到的政策,以及一系列為了提高移動裝置性能而專門添加的新選項。

  NSURLSession的另一重要組成部分是會話任務,它負責處理資料的加載,以及用戶端與伺服器之間的檔案和資料的上傳下載下傳服務。NSURLSessionTask與NSURLConnection是及其相似的,因為它負責加載資料,而主要的差別在于,任務共享它們父類NSURLSession的共同委托(common delegate)。

  我們現在首先深入探讨任務,然後再介紹更多關于會話配置的知識。

NSURLSessionTask

  NSURLSessionTask是一個抽象子類,它有三個具體的子類是可以直接使用的:NSURLSessionDataTask,NSURLSessionUploadTask和NSURLSessionDownloadTask。這三個類封裝了現代應用程式的三個基本網絡任務:擷取資料,比如JSON或XML,以及上傳下載下傳檔案。

  

iOS 之NSURLConnection和NSURLSession

  當一個NSURLSessionDataTask完成時,它具有關聯的資料,而一個NSURLSessionDownloadTask完成時,它具有一個已下載下傳檔案的臨時檔案路徑。 NSURLSessionUploadTask 繼承了 NSURLSessionDataTask,因為伺服器響應一個上傳請求時,往往伴随着相關聯的資料。 ???所有任務均可撤銷,也可以暫停和恢複。當一個下載下傳任務被取消時,它可以選擇建立恢複資料,然後可以傳遞給下一次新建立的下載下傳任務,以便繼續之前的下載下傳。

  不同于直接使用alloc-init‘d初始化方法,任務是由一個NSURLSession建立的。每個任務的構造方法都對應一個版本,有或者沒有completionHandler屬性,例如:–dataTaskWithRequest: 和–dataTaskWithRequest:completionHandler:。這與NSURLConnection的 -sendAsynchronousRequest:queue:completionHandler:類似,通過指定completionHandler屬性建立并使用一個隐含的委托,而不是使用任務的會話。在任何一種任務會話委托的預設行為需要被重寫的情況下,這種不太友善的非completionHandler的變體将需要被使用。

Constructors

  iOS5中,NSURLConnection添加了sendAsynchronousRequest:queue:completionHandler:方法,這大大簡化了一次性請求的使用,同時可以作為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與它的任務構造方法在此模式上疊代。在執行resume方法前,該任務對象為了進行進一步的配置而傳回,而不是立即執行resume方法。

  資料任務可以通過NSURL或NSURLRequest建立(前者是一個标準GET請求URL的快捷方式)。

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];      

  上傳任務也可以通過一個請求以及一個需要上傳的本地檔案的URL對應的NSData對象建立。

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];      

  下載下傳任務也需要一個請求,但不同之處在于它們的completionHandler。資料和上傳任務在完成時立即傳回,但下載下傳任務将資料寫入本地的臨時檔案。completionHandler有責任将檔案從它的臨時位置移動到一個永久位置,這個永久位置就是塊的傳回值。

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];
        return [documentsDirectoryURL URLByAppendingPathComponent:[[response URL] lastPathComponent]];
    }];

 [downloadTask resume];      

NSURLSession & NSURLConnection Delegate Methods

  總體而言,NSURLSession的委托方法,是NSURLConnection的演化的十年中 ad-hoc 模式出現以來的一個顯著改善。對于一個完整的概述,可以檢視此映射表。

  以下是一些具體的觀察:

  NSURLSession同時具有用來處理身份驗證挑戰會話和任務委托方法。這個會話的委托方法處理連接配接級别的問題,如伺服器信任和用戶端證書的評估,NTLM和Kerberos,而任務的委托處理以請求為基礎的挑戰,如Basic, Digest, 或者代理身份驗證。

  NSURLConnection由兩個方法可以表明一個請求已經完成(NSURLConnectionDataDelegate -connectionDidFinishLoading: 和 NSURLConnectionDelegate -connection:didFailWithError:),而NSURLSession隻有一個委托方法(NSURLSessionTaskDelegate -URLSession:task:didCompleteWithError:)。

  與NSURLConnection使用的 long long類型相比,委托方法指定在NSURLSession中一定數量的位元組傳輸使用int64_t類型的參數。

  NSURLSession在Foundation架構對于委托方法的completionHandler:參數使用上 ,引入了一種新的模式。這允許委托方法可以安全地在主線程以非阻塞方式運作;委托可以簡單地在背景運作dispatch_async ,然後在完成時調用completionHandler。同時,它可以有效地擁有多個傳回值,不需要使用笨拙的參數指針。就NSURLSessionTaskDelegate的URLSession:task:didReceiveChallenge:completionHandler:方法而言,completionHandler接受兩個參數:身份驗證質詢的處理( the authentication challenge disposition)以及需用使用的證書(如果适用)。

  想要檢視更多關于會話任務的資訊,可以檢視 WWDC Session 705: “What’s New in Foundation Networking”

NSURLSessionConfiguration

  NSURLSessionConfiguration對象用于初始化NSURLSession對象。展開請求級别中與NSMutableURLRequest相關的可供選擇的方案,我們可以看到NSURLSessionConfiguration對于會話如何産生請求,提供了相當多的控制和靈活性。從網絡通路性能,到cookie,安全性,緩存政策,自定義協定,啟動事件設定,以及用于移動裝置優化的幾個新屬性,你會發現你一直在尋找的,正是NSURLSessionConfiguration。

  會話在初始化時複制它們的配置,NSURLSession有一個隻讀的配置屬性,使得該配置對象上的變化對這個會話的政策無效。配置在初始化時被讀取一次,之後都是不會變化的。

Constructors

  NSURLSessionConfiguration有三個類構造函數,這很好地說明了NSURLSession是為不同的用例而設計的。

  + defaultSessionConfiguration傳回标準配置,這實際上與NSURLConnection的網絡協定棧是一樣的,具有相同的共享NSHTTPCookieStorage,共享NSURLCache和共享NSURLCredentialStorage。

  + ephemeralSessionConfiguration傳回一個預設配置,沒有持久性存儲的緩存,Cookie或證書。這對于實作像秘密浏覽功能的功能來說,是很理想的。

  + backgroundSessionConfiguration:獨特之處在于,它會建立一個背景會話。背景會話不同于正常的,普通的會話,它甚至可以在應用程式挂起,退出,崩潰的情況下運作上傳和下載下傳任務。初始化時指定的辨別符,被用于向任何可能在程序外恢複背景傳輸的守護程序提供上下文。

  想要檢視更多關于背景會話的資訊,可以檢視WWDC Session 204: “What’s New with Multitasking”

Properties

  NSURLSessionConfiguration擁有20個屬性。熟練掌握這些屬性的用處,将使應用程式充分利用其網絡環境。

General

  HTTPAdditionalHeaders指定了一組預設的可以設定出站請求的資料頭。這對于跨會話共享資訊,如内容類型,語言,使用者代理,身份認證,是很有用的。

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對标準的網絡流量,網絡電話,語音,視訊,以及由一個背景程序使用的流量進行了區分。大多數應用程式都不需要設定這個。

  allowsCellularAccess 和 discretionary 被用于節省通過蜂窩連接配接的帶寬。建議在使用背景傳輸的時候,使用discretionary屬性,而不是allowsCellularAccess屬性,因為它會把WiFi和電源可用性考慮在内。

  timeoutIntervalForRequest 和 timeoutIntervalForResource指定了請求以及該資源的逾時時間間隔。許多開發人員試圖使用timeoutInterval去限制發送請求的總時間,但這誤會了timeoutInterval的意思:封包之間的時間。timeoutIntervalForResource實際上提供了整體逾時的特性,這應該隻用于背景傳輸,而不是使用者實際上可能想要等待的任何東西。

  HTTPMaximumConnectionsPerHost 是 Foundation 架構中URL加載系統的一個新的配置選項。它曾經被用于NSURLConnection管理私人連接配接池。現在有了NSURLSession,開發者可以在需要時限制連接配接到特定主機的數量。

  HTTPShouldUsePipelining 也出現在NSMutableURLRequest,它可以被用于開啟HTTP管道,這可以顯着降低請求的加載時間,但是由于沒有被伺服器廣泛支援,預設是禁用的。

  sessionSendsLaunchEvents 是另一個新的屬性,該屬性指定該會話是否應該從背景啟動。

  connectionProxyDictionary指定了會話連接配接中的代理伺服器。同樣地,大多數面向消費者的應用程式都不需要代理,是以基本上不需要配置這個屬性。

  關于連接配接代理的更多資訊可以在 

CFProxySupport

 Reference 找到。

Cookie Policies

  HTTPCookieStorage 是被會話使用的cookie存儲。預設情況下,NSHTTPCookieShorage的+ sharedHTTPCookieStorage會被使用,這與NSURLConnection是相同的。

  HTTPCookieAcceptPolicy 決定了該會話應該接受從伺服器發出的cookie的條件。

  HTTPShouldSetCookies 指定了請求是否應該使用會話HTTPCookieStorage的cookie。

Security Policies

  URLCredentialStorage 是會話使用的證書存儲。預設情況下,NSURLCredentialStorage 的+ sharedCredentialStorage 會被使用使用,這與NSURLConnection是相同的。

  TLSMaximumSupportedProtocol 和 TLSMinimumSupportedProtocol 确定是否支援SSLProtocol版本的會話。

Caching Policies

  URLCache 是會話使用的緩存。預設情況下,NSURLCache 的+ sharedURLCache 會被使用,這與NSURLConnection是相同的。

  requestCachePolicy 指定了一個請求的緩存響應應該在什麼時候傳回。這相當于NSURLRequest 的-cachePolicy方法。

Custom Protocols

  protocolClasses是注冊NSURLProtocol類的特定會話數組。

總結

  iOS 7 和 Mac OS X 10.9 Mavericks 中URL加載系統的變化,是NSURLConnection的一個深思熟慮而自然的進化。總體而言,Foundation架構團隊做出了令人驚訝的工作,他們研究并預測了移動開發者現有的和新興的用例,創造了能夠滿足日常任務的, 真正有用的APIs。

  就可組合性和可擴充性而言,盡管在會話任務的體系結構中,某些決定是一種倒退,NSURLSession仍然可以很好地作為更進階别的網絡功能的一個基礎。