天天看點

ios NSURLSession使用說明及背景工作流程分析

NSURLSession是iOS7中新的網絡接口,它與咱們熟悉的NSURLConnection是并列的。在程式在前台時,NSURLSession與NSURLConnection可以互為替代工作。注意,如果使用者強制将程式關閉,NSURLSession會斷掉。

NSURLSession提供的功能:

1.通過URL将資料下載下傳到記憶體

2.通過URL将資料下載下傳到檔案系統

3.将資料上傳到指定URL

4.在背景完成上述功能

工作流程

如果我們需要利用NSURLSession進行資料傳輸我們需要:

1.建立一個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請求):

4.當不再需要連接配接調用Session的invalidateAndCancel直接關閉,或者調用finishTasksAndInvalidate等待目前Task結束後關閉。這時Delegate會收到URLSession:didBecomeInvalidWithError:這個事件。Delegate收到這個事件之後會被解引用。

5.如果是一個BackgroundSession,在Task執行的時候,使用者切到背景,Session會和ApplicationDelegate做互動。當程式切到背景後,在BackgroundSession中的Task還會繼續下載下傳,這部分文檔叙述比較少,現在分三個場景分析下Session和Application的關系:

1)當加入了多個Task,程式沒有切換到背景。

這種情況Task會按照NSURLSessionConfiguration的設定正常下載下傳,不會和ApplicationDelegate有互動。

2)當加入了多個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的時候調用一下,如下:

  1. @implementation APLAppDelegate 
  2. - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier 
  3.   completionHandler:(void (^)())completionHandler 
  4.     BLog(); 
  5.     /* 
  6.      Store the completion handler. The completion handler is invoked by the view controller's checkForAllDownloadsHavingCompleted method (if all the download tasks have been completed). 
  7.      */ 
  8.       self.backgroundSessionCompletionHandler = completionHandler; 
  9. //…… 
  10. @end 
  11. //Session的Delegate 
  12. @implementation APLViewController 
  13. - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session 
  14.     APLAppDelegate *appDelegate = (APLAppDelegate *)[[UIApplication sharedApplication] delegate]; 
  15.     if (appDelegate.backgroundSessionCompletionHandler) { 
  16.         void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler; 
  17.         appDelegate.backgroundSessionCompletionHandler = nil; 
  18.         completionHandler(); 
  19.     } 
  20.     NSLog(@"All tasks are finished"); 

3)當加入了多個Task,程式切到背景,下載下傳完成了幾個Task,然後使用者又切換到前台。(程式沒有退出)

  

切到背景之後,Session的Delegate仍然收不到消息。在下載下傳完成幾個Task之後再切換到前台,系統會先彙報已經下載下傳完成的Task的情況,然後繼續下載下傳沒有下載下傳完成的Task,後面的過程同第一種情況。

4)當加入了多個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(或者說沒有考慮全我上述的背景情況),但是大家有興趣可以關注一下他們後續的更新情況。

[objc] ​​view plain​​​​copy​​

  1. //////////////////////  

代碼示範

 01.URLSession 上傳,注意代理是 NSURLSessionTaskDelegate

  1. //  
  2. //  MJViewController.m  
  3. //  01.URLSession 上傳  
  4. //  Created by apple on 14-4-30.  
  5. //  Copyright (c) 2014年 itcast. All rights reserved.  
  6. #import "MJViewController.h"  
  7. @interface MJViewController () <NSURLSessionTaskDelegate>  
  8. @end  
  9. @implementation MJViewController  
  10. - (void)viewDidLoad  
  11. {  
  12.     [super viewDidLoad];  
  13.     [self uploadFile1];  
  14. }  
  15. #pragma mark - 監控上傳進度  
  16. - (void)uploadFile1  
  17.     // 1. URL  
  18.     NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"head8.png" withExtension:nil];  
  19.     NSURL *url = [NSURL URLWithString:@"http://localhost/uploads/1.png"];  
  20.     // 2. Request  
  21.     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:2.0f];  
  22.     // 1> PUT方法  
  23.     // PUT  
  24.     //    1) 檔案大小無限制  
  25.     //    2) 可以覆寫檔案  
  26.     // POST  
  27.     //    1) 通常有限制2M  
  28.     //    2) 建立檔案,不能重名  
  29.     request.HTTPMethod = @"PUT";  
  30.     // 2> 安全認證  
  31.     // admin:123456  
  32.     // result base64編碼  
  33.     // Basic result  
  34.     /** 
  35.      BASE 64是網絡傳輸中最常用的編碼格式 - 用來将二進制的資料編碼成字元串的編碼方式 
  36.      BASE 64的用法: 
  37.      1> 能夠編碼,能夠解碼 
  38.      2> 被很多的加密算法作為基礎算法 
  39.      */  
  40.     NSString *authStr = @"admin:123456";  
  41.     NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding];  
  42.     NSString *base64Str = [authData base64EncodedStringWithOptions:0];  
  43.     NSString *resultStr = [NSString stringWithFormat:@"Basic %@", base64Str];  
  44.     [request setValue:resultStr forHTTPHeaderField:@"Authorization"];  
  45.     // 3. Session,全局單例(我們能夠給全局的session設定代理嗎?如果不能為什麼?)  
  46.     // sharedSession是全局共享的,是以如果要設定代理,需要單獨執行個體化一個Session  
  47.      NSURLSessionConfiguration(會話配置) 
  48.      defaultSessionConfiguration;       // 磁盤緩存,适用于大的檔案上傳下載下傳 
  49.      ephemeralSessionConfiguration;     // 記憶體緩存,以用于小的檔案互動,GET一個頭像 
  50.      backgroundSessionConfiguration:(NSString *)identifier; // 背景上傳和下載下傳 
  51.     NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];  
  52.     NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc]init]];  
  53.     // 需要監聽任務的執行狀态  
  54.     NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromFile:fileURL];  
  55.     // 4. resume  
  56.     [task resume];  
  57. #pragma mark - 上傳進度的代理方法  
  58. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend  
  59.     // bytesSent totalBytesSent totalBytesExpectedToSend  
  60.     // 發送位元組(本次發送的位元組數)    總發送位元組數(已經上傳的位元組數)     總希望要發送的位元組(檔案大小)  
  61.     NSLog(@"%lld-%lld-%lld-", bytesSent, totalBytesSent, totalBytesExpectedToSend);  
  62.     // 已經上傳的百分比  
  63.     float progress = (float)totalBytesSent / totalBytesExpectedToSend;  
  64.     NSLog(@"%f", progress);  
  65. #pragma mark - 上傳完成的代理方法  
  66. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error  
  67.     NSLog(@"完成 %@", [NSThread currentThread]);  

02.Session下載下傳

  1. //  02.Session下載下傳  
  2. @interface MJViewController () <NSURLSessionDownloadDelegate>  
  3. @property (weak, nonatomic) IBOutlet UIImageView *imageView;  
  4. /** 
  5.  // 下載下傳進度跟進 
  6.  - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
  7.  didWriteData:(int64_t)bytesWritten 
  8.  totalBytesWritten:(int64_t)totalBytesWritten 
  9.  totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite; 
  10.  didWriteData totalBytesWritten totalBytesExpectedToWrite 
  11.  本次寫入的位元組數 已經寫入的位元組數   預期下載下傳的檔案大小 
  12.  // 完成下載下傳 
  13.  didFinishDownloadingToURL:(NSURL *)location; 
  14.  */  
  15.     [self downloadTask];  
  16. #pragma mark - 下載下傳(GET)  
  17. - (void)downloadTask  
  18.     NSURL *url = [NSURL URLWithString:@"http://localhost/itcast/images/head1.png"];  
  19.     NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:2.0];  
  20.     // 3. Session  
  21.     NSURLSession *session = [NSURLSession sharedSession];  
  22.     // 4. download  
  23.     [[session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {  
  24.         // 下載下傳的位置,沙盒中tmp目錄中的臨時檔案,會被及時删除  
  25.         NSLog(@"下載下傳完成 %@ %@", location, [NSThread currentThread]);  
  26.         /** 
  27.          document       備份,下載下傳的檔案不能放在此檔案夾中 
  28.          cache          緩存的,不備份,重新啟動不會被清空,如果緩存内容過多,可以考慮建立一條線程檢查緩存目錄中的檔案大小,自動清理緩存,給使用者節省控件 
  29.          tmp            臨時,不備份,不緩存,重新啟動iPhone,會自動清空 
  30.          */  
  31.         // 直接通過檔案名就可以加載圖像,圖像會常駐記憶體,具體的銷毀有系統負責  
  32.         // [UIImage imageNamed:@""];  
  33.         dispatch_async(dispatch_get_main_queue(), ^{  
  34.             // 從網絡下載下傳下來的是二進制資料  
  35.             NSData *data = [NSData dataWithContentsOfURL:location];  
  36.             // 這種方式的圖像會自動釋放,不占據記憶體,也不需要放在臨時檔案夾中緩存  
  37.             // 如果使用者需要,可以提供一個功能,儲存到使用者的相冊即可  
  38.             UIImage *image = [UIImage imageWithData:data];  
  39.             self.imageView.image = image;  
  40.         });  
  41.     }] resume];  
  42. //    [task resume];