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的時候調用一下,如下:
- @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");
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 plaincopy
- //////////////////////
代碼示範
01.URLSession 上傳,注意代理是 NSURLSessionTaskDelegate
- //
- // MJViewController.m
- // 01.URLSession 上傳
- // Created by apple on 14-4-30.
- // Copyright (c) 2014年 itcast. All rights reserved.
- #import "MJViewController.h"
- @interface MJViewController () <NSURLSessionTaskDelegate>
- @end
- @implementation MJViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- [self uploadFile1];
- }
- #pragma mark - 監控上傳進度
- - (void)uploadFile1
- // 1. URL
- NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"head8.png" withExtension:nil];
- NSURL *url = [NSURL URLWithString:@"http://localhost/uploads/1.png"];
- // 2. Request
- NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:2.0f];
- // 1> PUT方法
- // PUT
- // 1) 檔案大小無限制
- // 2) 可以覆寫檔案
- // POST
- // 1) 通常有限制2M
- // 2) 建立檔案,不能重名
- request.HTTPMethod = @"PUT";
- // 2> 安全認證
- // admin:123456
- // result base64編碼
- // Basic result
- /**
- BASE 64是網絡傳輸中最常用的編碼格式 - 用來将二進制的資料編碼成字元串的編碼方式
- BASE 64的用法:
- 1> 能夠編碼,能夠解碼
- 2> 被很多的加密算法作為基礎算法
- */
- NSString *authStr = @"admin:123456";
- NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding];
- NSString *base64Str = [authData base64EncodedStringWithOptions:0];
- NSString *resultStr = [NSString stringWithFormat:@"Basic %@", base64Str];
- [request setValue:resultStr forHTTPHeaderField:@"Authorization"];
- // 3. Session,全局單例(我們能夠給全局的session設定代理嗎?如果不能為什麼?)
- // sharedSession是全局共享的,是以如果要設定代理,需要單獨執行個體化一個Session
- NSURLSessionConfiguration(會話配置)
- defaultSessionConfiguration; // 磁盤緩存,适用于大的檔案上傳下載下傳
- ephemeralSessionConfiguration; // 記憶體緩存,以用于小的檔案互動,GET一個頭像
- backgroundSessionConfiguration:(NSString *)identifier; // 背景上傳和下載下傳
- NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
- NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc]init]];
- // 需要監聽任務的執行狀态
- NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromFile:fileURL];
- // 4. resume
- [task resume];
- #pragma mark - 上傳進度的代理方法
- - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
- // bytesSent totalBytesSent totalBytesExpectedToSend
- // 發送位元組(本次發送的位元組數) 總發送位元組數(已經上傳的位元組數) 總希望要發送的位元組(檔案大小)
- NSLog(@"%lld-%lld-%lld-", bytesSent, totalBytesSent, totalBytesExpectedToSend);
- // 已經上傳的百分比
- float progress = (float)totalBytesSent / totalBytesExpectedToSend;
- NSLog(@"%f", progress);
- #pragma mark - 上傳完成的代理方法
- - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
- NSLog(@"完成 %@", [NSThread currentThread]);
02.Session下載下傳
- // 02.Session下載下傳
- @interface MJViewController () <NSURLSessionDownloadDelegate>
- @property (weak, nonatomic) IBOutlet UIImageView *imageView;
- /**
- // 下載下傳進度跟進
- - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
- didWriteData:(int64_t)bytesWritten
- totalBytesWritten:(int64_t)totalBytesWritten
- totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
- didWriteData totalBytesWritten totalBytesExpectedToWrite
- 本次寫入的位元組數 已經寫入的位元組數 預期下載下傳的檔案大小
- // 完成下載下傳
- didFinishDownloadingToURL:(NSURL *)location;
- */
- [self downloadTask];
- #pragma mark - 下載下傳(GET)
- - (void)downloadTask
- NSURL *url = [NSURL URLWithString:@"http://localhost/itcast/images/head1.png"];
- NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:2.0];
- // 3. Session
- NSURLSession *session = [NSURLSession sharedSession];
- // 4. download
- [[session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
- // 下載下傳的位置,沙盒中tmp目錄中的臨時檔案,會被及時删除
- NSLog(@"下載下傳完成 %@ %@", location, [NSThread currentThread]);
- /**
- document 備份,下載下傳的檔案不能放在此檔案夾中
- cache 緩存的,不備份,重新啟動不會被清空,如果緩存内容過多,可以考慮建立一條線程檢查緩存目錄中的檔案大小,自動清理緩存,給使用者節省控件
- tmp 臨時,不備份,不緩存,重新啟動iPhone,會自動清空
- */
- // 直接通過檔案名就可以加載圖像,圖像會常駐記憶體,具體的銷毀有系統負責
- // [UIImage imageNamed:@""];
- dispatch_async(dispatch_get_main_queue(), ^{
- // 從網絡下載下傳下來的是二進制資料
- NSData *data = [NSData dataWithContentsOfURL:location];
- // 這種方式的圖像會自動釋放,不占據記憶體,也不需要放在臨時檔案夾中緩存
- // 如果使用者需要,可以提供一個功能,儲存到使用者的相冊即可
- UIImage *image = [UIImage imageWithData:data];
- self.imageView.image = image;
- });
- }] resume];
- // [task resume];