1. 程式人生 > >從 NSURLConnection 到 NSURLSession

從 NSURLConnection 到 NSURLSession

前言

現如今的移動應用開發,網路模組幾乎成了標配。如果你是早期 iOS 開發者的話,那麼你對 NSURLConnection一定不會陌生。但其操作起來有許多不便,這也使得大家更願意使用第三方庫的解決方案,比如大名鼎鼎的AFNetworking 你一定有所耳聞。正是因為這一點,蘋果隨著 iOS 7 的釋出,也為開發者帶來了改進後的原生網路庫支援,那就是 NSURLSession。

今天,就讓我來給你道一道從 NSURLConnection 到 NSURLSession 那些你知道和不知道的事。


NSURLConnection

NSURLConnection 作為 Core Foundation / CFNetwork 框架的 API 之上的一個抽象,在 2003 年,隨著第一版的 Safari 的釋出就釋出了。NSURLConnection 這個名字,實際上是指代的 Foundation 框架的 URL 載入系統中一系列相關聯的元件:NSURLRequestNSURLResponseNSURLProtocol、 NSURLCache、 NSHTTPCookieStorageNSURLCredentialStorage 以及同名類 NSURLConnection

NSURLRequest 被傳遞給 NSURLConnection。被委託物件(遵守以前的非正式協議 <NSURLConnectionDelegate> 和 <NSURLConnectionDataDelegate>)非同步地返回一個 NSURLResponse 以及包含伺服器返回資訊的 NSData

在一個請求被髮送到伺服器之前,系統會先查詢共享的快取資訊,然後根據策略(policy)以及可用性(availability)的不同,一個已經被快取的響應可能會被立即返回。如果沒有快取的響應可用,則這個請求將根據我們指定的策略來快取它的響應以便將來的請求可以使用。

在把請求傳送給伺服器的過程中,伺服器可能會發出鑑權查詢(authentication challenge),這可以由共享的 cookie 或機密儲存(credential storage)來自動響應,或者由被委託物件來響應。傳送中的請求也可以被註冊的NSURLProtocol 物件所攔截,以便在必要的時候無縫地改變其載入行為。

使用步驟

概覽

圖片來自

NSURL

建立一個 NSURL 物件,設定請求路徑:

1
NSURL *url = [NSURL URLWithString:@"協議://主機地址/路徑?引數&引數"];

解釋如下:

  • 協議:不同的協議,代表著不同的資源查詢方式、資源傳輸方式,比如常用的 HTTP、FTP 等
  • 主機地址:存放資源的主機的 IP 地址(域名)
  • 路徑:資源在主機中的具體位置
  • 引數:引數可有可無,也可以多個。如果帶引數的話,用 “?” 號後面接引數,多個引數的話之間用 “&” 隔開

NSURLRequest

建立一個 NSURLRequest 物件並傳入 NSURL,設定請求頭和請求體:

1
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];

引數解釋如下:

  • requestWithURL:資源路徑
  • cachePolicy:快取策略(無論使用哪種快取策略,都會在本地快取資料),型別為列舉型別,取值如下:
    • NSURLRequestUseProtocolCachePolicy = 0 // 預設的快取策略,使用協議的快取策略
    • NSURLRequestReloadIgnoringLocalCacheData = 1 // 每次都從網路載入
    • NSURLRequestReturnCacheDataElseLoad = 2 // 返回快取否則載入,很少使用
    • NSURLRequestReturnCacheDataDontLoad = 3 // 只返回快取,沒有也不載入,很少使用
  • timeoutInterval:超時時長,預設 60s

另外,還可以設定其它一些資訊,比如請求頭,請求體等等,如下:

1
2
3
4
5
// 告訴伺服器資料為 JSON 型別
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 
// 設定請求體(JSON型別)
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{@"userid":@"123456"} options:NSJSONWritingPrettyPrinted error:nil];
request.HTTPBody = jsonData;

注意,上面的 request 應為 NSMutableURLRequest,即可變型別。(使用 POST 請求的話,那麼就必須的使用NSMutableURLRequest

NSURLConnection

使用 NSURLConnection 傳送請求,通過返回 NSURLResponse 例項和 NSError 例項分析結果,接受伺服器返回資料。

NSURLConnection 預設的請求型別為 GET,下面分非同步和同步介紹 GET 的使用(POST 會在 NSURLSession 中介紹)。

非同步請求

非同步是指:在傳送請求之後,一邊在子執行緒中接收返回資料,一邊執行之後的程式碼,當返回資料接收完畢後,採用回撥的方式通知主執行緒做處理。同時,非同步請求有兩種實現方式。

1.使用 block:

1
2
3
4
5
6
7
8
9
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    // 有的時候,伺服器訪問正常,但是會沒有資料
    // 以下的 if 是比較標準的錯誤處理程式碼
    if (connectionError != nil || data == nil) {
        //給使用者的提示資訊
        NSLog(@"網路不給力");
        return;
    }
}];

引數說明如下:

  • completionHandler:請求響應後(或者請求超時)執行的程式碼,queue 為程式碼新增到的佇列,即 block執行的執行緒
    • NSURLResponse 為伺服器的響應,真實型別為 NSHTTPURLResponse,通常只在「下載」功能時,才會使用;下面是協議頭的引數:
      • URL:響應的 URL,有的時候,訪問一個 URL 地址,伺服器可能會出現重定向,會定位到新的地址
      • MIMEType(Content-Type):伺服器告訴客戶端,可以用什麼軟體開啟二進位制資料。網路之所以豐富多采,是因為有豐富的客戶端軟體。栗子:Windows 上提示安裝 Flash 外掛
      • expectedContentLength:預期的內容長度,要下載的檔案長度,下載檔案時非常有用
      • suggestedFilename:「建議」的檔名,方便使用者直接儲存,很多時候,使用者並不關心要儲存成什麼名字
      • textEncodingName:文字的編碼名稱 @”UTF8”,大多數都是 UTF8
      • statusCode:狀態碼,在做下載操作的時候,需要判斷一下
      • allHeaderFields:所有的響應頭字典時候,使用者並不關心要儲存成什麼名字
  • NSData:伺服器返回的資料,例如:JSON、XML
  • NSError:網路訪問錯誤碼

2.使用代理(Delegate):

1
2
3
@interface ViewController ()<NSURLConnectionDataDelegate>

@end
1
2
3
NSURL * url = [NSURL URLWithString:@"http://itangqi.me"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];

使用代理可以監測下載過程:

1
2
3
4
5
6
7
8
9
10
11
12
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 開始接收資料
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    // 正在接收資料(會呼叫多次)
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    // 接收資料失敗
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
    // 接收資料完成(成功|失敗)
}

同步

同步是指:資料的請求在主執行緒來執行,一旦傳送同步請求,直至伺服器返回資料完成,才可以進行下一步操作,而網路資料載入需要一個時間過程,這樣的話就會堵塞主執行緒,當然這種使用場景很少。

1
2
3
// 同步請求,程式碼會阻塞在這裡一直等待伺服器返回,如果 data 為 nil 則請求失敗,當獲取少量資料時可以使用此方法。
// request 引數同上。
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];

NSURLSession

不管怎樣,NSURLConnection 作為網路基礎架構,已經服務了成千上萬的 iOS 和 Mac OS 程式,並且做的還算相當不錯。但是這些年,一些用例——尤其是在 iPhone 和 iPad 上面——已經對 NSURLConnection 的幾個核心概念提出了挑戰,讓蘋果有理由對它進行重構。

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

和 NSURLConnection 一樣,NSURLSession 指的也不僅是同名類 NSURLSession,還包括一系列相互關聯的類。NSURLSession 包括了與之前相同的元件,NSURLRequest 與 NSURLCache,但是把 NSURLConnection 替換成了 NSURLSessionNSURLSessionConfiguration 以及 NSURLSessionTask 的 3 個子類:NSURLSessionDataTaskNSURLSessionUploadTaskNSURLSessionDownloadTask

與 NSURLConnection 相比,NSURLsession 最直接的改進就是可以配置每個 session 的快取,協議,cookie,以及證書策略(credential policy),甚至跨程式共享這些資訊。這將允許程式和網路基礎框架之間相互獨立,不會發生干擾。每個 NSURLSession 物件都由一個 NSURLSessionConfiguration 物件來進行初始化,後者指定了剛才提到的那些策略以及一些用來增強移動裝置上效能的新選項。

NSURLSession 中另一大塊就是 session task。它負責處理資料的載入以及檔案和資料在客戶端與服務端之間的上傳和下載。NSURLSessionTask 與 NSURLConnection 最大的相似之處在於它也負責資料的載入,最大的不同之處在於所有的 task 共享其創造者 NSURLSession 這一公共委託者(common delegate)。

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

概覽

圖片來自

Sessions

1
2
3
4
5
6
7
8
// 使用靜態的 sharedSession 的方法,該類使用共享的 seesion,該 seesion 使用全域性的 Cache,Cookie 和證書
+ (NSURLSession *)sharedSession;  

// 根據 NSURLSessionConfiguration 建立對應配置的 seesion
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration; 

// 也是根據 NSURLSessionConfiguration 建立對應配置的 seesion,並且可以指定 seesion 的委託和委託所處的佇列
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue;

NSURLSessionTask

NSURLSession 本身是不會進行請求的,而是通過建立 task 的形式進行網路請求,同一個 NSURLSession 可以建立多個 task,並且這些 task 之間的 cache 和 cookie 是共享的。那麼我們就來看看 NSURLSession 都能建立哪些 task 吧。

Task 可以翻譯為任務,那麼在和網路請求相關的任務中,我們可以理解為:資料請求任務、下載任務、上傳任務等。

其實從類名就能猜出它們各自的用途:

  1. NSURLSessionDataTask:使用這個 task 來呼叫 HTTP GET 方式請求,從伺服器獲取資料到記憶體。返回的資料格式是 NSData,可根據需要自行轉換成 XML、JSON 等資料格式

  2. NSURLSessionUploadTask:使用這個 task 來上傳磁碟檔案到 web 伺服器,典型地通過 HTTP POST 或者 PUT 方式

  3. NSURLSessionDownloadTask:使用這個 task 來從遠端伺服器下載檔案到臨時檔案地址

  4. NSURLSessionStreamTask:使用這個 task 來執行非同步的讀和寫

你也能暫停,恢復(開始)和取消 tasks。

NSURLSessionDataTask POST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 *  簡單 Post 請求,POST 和 GET 請求在於對 request 的處理不同,其餘和 GET 相同
 */
- (void)postWithSharedSession {
  // 獲取預設 Session
  NSURLSession *session = [NSURLSession sharedSession];
  // 建立 URL
  NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
  // 建立 request
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  // 請求方法
  request.HTTPMethod = @"POST";
  // 請求體
  request.HTTPBody = [@"username=1234&pwd=4321" dataUsingEncoding:NSUTF8StringEncoding];
  // 建立任務 task
  NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  // 獲取資料後解析並輸出
  NSLog(@"%@",[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
  }];
  // 啟動任務
  [task resume];
}

NSURLSessionDataDelegate 代理方法

NSURLSession 提供了 block 的方式處理返回的資料,但是如果我們想要在接收資料的過程中處理資料,我們可以使用 NSURLSessionDataDelegate 的代理方法,分為接收響應、接收資料和請求完成三個階段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 - (void)sessionDataDelegate {
 // 建立帶有代理方法的自定義 session
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
 
 // 建立任務
NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=1234&pwd=4321"]]];
 
  // 啟動任務
[task resume];
}
 
// 1. 接受到伺服器的響應
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
  NSLog(@"任務完成");
  // 必須設定對響應進行允許處理才會執行後面兩個操作。
  completionHandler(NSURLSessionResponseAllow);
}
 
// 2. 接收到伺服器的資料(可能呼叫多次)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
  // 處理每次接收的資料
  NSLog(@"%s",__func__);
}
 
// 3. 請求成功或者失敗(如果失敗,error有值)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // 請求完成,成功或者失敗的處理
    NSLog(@"SessionTask %s",__func__);
}

NSURLSessionConfiguration

NSURLSessionConfiguration 物件用於對 NSURLSession 物件進行初始化。從指定可用網路,到 cookie,安全性,快取策略,再到使用自定義協議,啟動事件的設定,以及用於移動裝置優化的幾個新屬性,你會發現使用NSURLSessionConfiguration 可以找到幾乎任何你想要進行配置的選項。其有三個類工廠方法:

  1. 預設模式(+defaultSessionConfiguration):返回一個標準的 configuration,工作模式類似於 NSURLConnection,可以使用快取的 Cache,Cookie,證書(credential)

  2. 及時模式(+ephemeralSessionConfiguration): 臨時 session 配置,與預設配置相比,這個配置不會使用快取的 Cache,Cookie,證書,只會存在記憶體裡,所以當程式退出時,所有的資料都會消失,這對於實現像祕密瀏覽這種功能來說是很理想的

  3. 後臺模式(+backgroundSessionConfiguration):它會建立一個後臺 session。後臺 session 不同於常規的,普通的 session,它甚至可以在應用程式掛起,退出或者崩潰的情況下執行上傳和下載任務。初始化時指定的識別符號,被用於向任何可能在程序外恢復後臺傳輸的守護程序(daemon)提供上下文

除了這三種預設的模式之外 NSURLSessionConfiguration 還可以進行很多的配置。 timeoutIntervalForRequest 和 timeoutIntervalForResource 可以控制網路操作的超時時間。 allowsCellularAccess 屬性可以控制是否允許使用無線網路。HTTPAdditionalHeaders 可以指定 HTTP 請求頭。

NSURLSessionConfiguration 幾乎可以完成網路操作的大多數配置功能,並且這些配置都繫結到當前的 Session 中,我們一旦用配置好的 NSURLSessionConfiguration 初始化 NSURLSession 例項後,就不能修改這個 NSURLSession 相關的配置了。所以,一切的配置操作都放在初始化 NSURLSession 之前。


iOS 9

  1. 在 iOS 7 後,蘋果推薦使用 NSURLSession,並在 iOS 9 中廢棄了 NSURLConnection

  2. iOS 9 所有網路連線預設為 HTTPS 服務,為此推出 App Transport Security,詳情可參見:Networking with NSURLSession


NSURLConnection vs. NSURLSession

  1. 後臺上傳和下載:只需在建立 NSURLSession 的時候配置一個選項,就能得到後臺網路的所有好處。這樣可以延長電池壽命,並且還支援 UIKit 的多 task,在程序間使用相同的委託模型

  2. 能夠暫停和恢復網路操作:使用 NSURLSession API 能夠暫停,停止,恢復所有的網路任務,再也完全不需要子類化 NSOperation

  3. 可配置的容器:對於 NSURLSession 裡面的 requests 來說,每個NSURLSession 都是可配置的容器。舉個例來說,假如你需要設定 HTTP header 選項,你只用做一次,session 裡面的每個 request 就會有同樣的配置

  4. 提高認證處理:認證是在一個指定的連線基礎上完成的。在使用 NSURLConnection 時,如果發出一個訪問,會返回一個任意的 request。此時,你就不能確切的知道哪個 request 收到了訪問。而在 NSURLSession 中,就能用代理處理認證

  5. 豐富的代理模式:在處理認證的時候,NSURLConnection 有一些基於非同步的 block 方法,但是它的代理方法就不能處理認證,不管請求是成功或是失敗。在 NSURLSession 中,可以混合使用代理和 block 方法處理認證

  6. 上傳和下載通過檔案系統:它鼓勵將資料(檔案內容)從元資料(URL 和 settings)中分離出來

要點

  1. 請求的快取策略使用 NSURLRequestReloadIgnoringCacheData,忽略本地快取

  2. 伺服器響應結束後,要記錄 Etag,伺服器內容和本地快取對比是否變化的重要依據

  3. 在傳送請求時,設定 If-None-Match,並且傳入 etag

  4. 連線結束後,要判斷響應頭的狀態碼,如果是 304,說明本地快取內容沒有發生變化,此時可以使用本地快取來載入資料,如果快取檔案被意外刪除,程式依然執行但會報錯,載入資料也失敗

  5. GET 快取的資料會儲存在 Cache 目錄中 /bundleId 下,Cache.db 中

原文連結:http://itangqi.me/2016/04/01/from-nsurlconnection-to-nsurlsession/