NSURLSession簡介
NSURLSession是iOS7中新的網絡接口,它與咱們熟悉的NSURLConnection是並列的。在程序在前台時,NSURLSession與NSURLConnection可以互為替代工作。注意,如果用戶強制將程序關閉,NSURLSession會斷掉。
NSURLSession提供的功能:
- 通過URL將數據下載到內存
- 通過URL將數據下載到文件系統
- 將數據上傳到指定URL
- 在后台完成上述功能
工作流程
如果我們需要利用NSURLSession進行數據傳輸我們需要:
- 創建一個NSURLSessionConfiguration,用於第二步創建NSSession時設置工作模式和網絡設置:
工作模式分為:
- 一般模式(default):工作模式類似於原來的NSURLConnection,可以使用緩存的Cache,Cookie,鑒權。
- 及時模式(ephemeral):不使用緩存的Cache,Cookie,鑒權。
- 后台模式(background):在后台完成上傳下載,創建Configuration對象的時候需要給一個NSString的ID用於追蹤完成工作的Session是哪一個(后面會講到)。
網絡設置:參考NSURLConnection中的設置項。
1. 創建一個NSURLSession,系統提供了兩個創建方法:
- sessionWithConfiguration:
- sessionWithConfiguration:delegate:delegateQueue:
第一個粒度較低就是根據剛才創建的Configuration創建一個Session,系統默認創建一個新的OperationQueue處理Session的消息。
第二個粒度比較高,可以設定回調的delegate(注意這個回調delegate會被強引用),並且可以設定delegate在哪個OperationQueue回調,如果我們將其設置為[NSOperationQueue mainQueue]就能在主線程進行回調非常的方便。
2.創建一個NSURLRequest調用剛才的NSURLSession對象提供的Task函數,創建一個NSURLSessionTask。
根據職能不同Task有三種子類:
- NSURLSessionUploadTask:上傳用的Task,傳完以后不會再下載返回結果;
- NSURLSessionDownloadTask:下載用的Task;
- NSURLSessionDataTask:可以上傳內容,上傳完成后再進行下載。
得到的Task,調用resume開始工作。
3. 如果是細粒度的Session調用,Session與Delegate會在指定的OperationQueue中進行交互,以咱們下載例子,交互過程的順序圖如下(假如不需要鑒權,即非HTTPS請求):
5. 當不再需要連接調用Session的invalidateAndCancel直接關閉,或者調用finishTasksAndInvalidate等待當前Task結束后關閉。這時Delegate會收到URLSession:didBecomeInvalidWithError:這個事件。Delegate收到這個事件之后會被解引用。
6. 如果是一個BackgroundSession,在Task執行的時候,用戶切到后台,Session會和ApplicationDelegate做交互。當程序切到后台后,在BackgroundSession中的Task還會繼續下載,這部分文檔敘述比較少,現在分三個場景分析下Session和Application的關系:
- 當加入了多個Task,程序沒有切換到后台。
這種情況Task會按照NSURLSessionConfiguration的設置正常下載,不會和ApplicationDelegate有交互。
- 當加入了多個Task,程序切到后台,所有Task都完成下載。
在切到后台之后,Session的Delegate不會再收到,Task相關的消息,直到所有Task全都完成后,系統會調用ApplicationDelegate的application:handleEventsForBackgroundURLSession:completionHandler:回調,之后“匯報”下載工作,對於每一個后台下載的Task調用Session的Delegate中的URLSession:downloadTask:didFinishDownloadingToURL:(成功的話)和URLSession:task:didCompleteWithError:(成功或者失敗都會調用)。
之后調用Session的Delegate回調URLSessionDidFinishEventsForBackgroundURLSession:。
注意:在ApplicationDelegate被喚醒后,會有個參數ComplietionHandler,這個參數是個Block,這個參數要在后面Session的Delegate中didFinish的時候調用一下,如下:
@implementation APLAppDelegate
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler { BLog(); /* Store the completion handler. The completion handler is invoked by the view controller's checkForAllDownloadsHavingCompleted method (if all the download tasks have been completed). */ self.backgroundSessionCompletionHandler = completionHandler; } //…… @end
//Session的Delegate @implementation APLViewController
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { APLAppDelegate *appDelegate = (APLAppDelegate *)[[UIApplication sharedApplication] delegate]; if (appDelegate.backgroundSessionCompletionHandler) { void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler; appDelegate.backgroundSessionCompletionHandler = nil; completionHandler(); }
NSLog(@"All tasks are finished"); } @end |
- 當加入了多個Task,程序切到后台,下載完成了幾個Task,然后用戶又切換到前台。(程序沒有退出)
切到后台之后,Session的Delegate仍然收不到消息。在下載完成幾個Task之后再切換到前台,系統會先匯報已經下載完成的Task的情況,然后繼續下載沒有下載完成的Task,后面的過程同第一種情況。
- 當加入了多個Task,程序切到后台,幾個Task已經完成,但還有Task還沒有下載完的時候關掉強制退出程序,然后再進入程序的時候。(程序退出了)
最后這個情況比較有意思,由於程序已經退出了,后面沒有下完Session就不在了后面的Task肯定是失敗了。但是已經下載成功的那些Task,新啟動的程序也沒有聽“匯報”的機會了。經過實驗發現,這個時候之前在NSURLSessionConfiguration設置的NSString類型的ID起作用了,當ID相同的時候,一旦生成Session對象並設置Delegate,馬上可以收到上一次關閉程序之前沒有匯報工作的Task的結束情況(成功或者失敗)。但是當ID不相同,這些情況就收不到了,因此為了不讓自己的消息被別的應用程序收到,或者收到別的應用程序的消息,起見ID還是和程序的Bundle名稱綁定上比較好,至少保證唯一性。
總結
就像前面說的,在普通的應用場景下NSURLSession與NSURLConnection相比沒有什么優勢,但是在程序切換到后台之后Background的Session就顯得更加靈活了。
另外,現在主流的網絡開發框架AFNetworking已經更新到了2.0(只支持iOS 6 / iOS 7),其中最重要的一個更新就是添加了NSURLSession相關的支持。雖然就我現在(2013.10.13)看到他們的源碼中,還沒有完全的支持后台的Session(或者說沒有考慮全我上述的后台情況),但是大家有興趣可以關注一下他們后續的更新情況。
參考資料:
蘋果官方文檔:
注意:蘋果又開始不更新Xcode 中的文檔,然后悄悄在網上更新了,大家請關注相關文檔最后的更新時間,以最新的為准
AFNetworking 2.0:
https://github.com/AFNetworking/AFNetworking/