iOS7新特性-NSURLSession詳解

前言:
本文由DevDiv版主@jas 原創翻譯,轉載請註明出處!
原文:http://www.shinobicontrols.com/b ... day-1-nsurlsession/

你們都知道,過去的IOS系統網絡處理是經過NSURLConnection來實現的。因爲NSURLConnection經過全局狀態來管理cookies和認證信息,這就意味着在某種狀況下,可能同時存在兩個不一樣的鏈接去使用這些公共資源。NSURLSession很好的解決了許多這種相似的問題。

本文連同附件一共討論了三種不一樣的下載場景。本文會着重講述有關NSURLSession的部分,整個項目就再也不闡述了。代碼能夠在github回購。

NSURLSession狀態同時對應着多個鏈接,不像以前使用共享的一個全局狀態。會話是經過工廠方法來建立配置對象。
總共有三種會話:
1.        默認的,進程內會話
2.        短暫的(內存),進程內會話
3.        後臺會話

若是是簡單的下載,咱們只須要使用默認模式便可:
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];

配置對象有不少屬性。例如,能夠設置TLS安全等級,TLS決定你可使用cookies和超時時間。還有兩個很是有趣的屬性:allowsCellularAccess和discretionary。前一個屬性表示當只有一個3G網絡時,網絡是否容許訪問。設置discretionary屬性能夠控制系統在一個合適的時機訪問網絡,好比有可用的WiFi,有充足的電量。這個屬性主要針對後臺回話的,因此在後臺會話模式下默認是打開的。

當咱們建立了一個會話配置對象後,就能夠用它來建立會話對象了:

php

1 NSURLSession *inProcessSession;
2 inProcessSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];



注意:這裏咱們把本身設置爲代理了。經過代理方法能夠告訴咱們數據傳輸進度以及獲取認證信息。下面咱們會實現一些合適的代理。

數據傳輸時封裝在任務裏面的,這裏有三種類型:
1.        數據任務 (NSURLSessionDataTask) 
2.        上傳任務 (NSURLSessionUploadTask) 
3.        下載任務(NSURLSessionDownloadTask) 
在會話中傳輸數據時,咱們須要實現某一種任務。好比下載:

ios

1 NSString *url = @"http://appropriate/url/here";
2 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; NSURLSessionDownloadTask *cancellableTask = [inProcessSession downloadTaskWithRequest:request];
3 [cancellableTask resume];



如今會話將會異步下載此url的文件內容。

咱們須要實現一個代理方法來獲取這個下載的內容:

git

01 -        (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
02 -        {
03 -         // We've successfully finished the download. Let's save the file
04 -        NSFileManager *fileManager = [NSFileManager defaultManager];
05 -        NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
06 -        NSURL *documentsDirectory = URLs[0];
07 -        NSURL *destinationPath = [documentsDirectory URLByAppendingPathComponent:[location lastPathComponent]];
08 -        NSError *error;
09 -        // Make sure we overwrite anything that's already there
10 -        [fileManager removeItemAtURL:destinationPath error:NULL];
11 -        BOOL success = [fileManager copyItemAtURL:location toURL:destinationPath error:&error]; if (success)
12 -         {
13 -        dispatch_async(dispatch_get_main_queue(), ^{
14 -        UIImage *image = [UIImage imageWithContentsOfFile:[destinationPath path]]; self.imageView.image = image;
15 -        self.imageView.contentMode = UIViewContentModeScaleAspectFill;
16 -        self.imageView.hidden = NO; });
17 -        }
18 -        else
19 -        {
20 -         NSLog(@"Couldn't copy the downloaded file");
21 -        }
22 -        if(downloadTask == cancellableTask)
23 -        {
24 -        cancellableTask = nil;
25 -         }
26 -        }


這個方法在NSURLSessionDownloadTaskDelegate代理中。在代碼中,咱們獲取到下載文件的臨時目錄,並把它保存到文檔目錄下(由於有個圖片),而後顯示給用戶。

上面的代理是下載成功的回調方法。下面代理方法也在NSURLSessionDownloadTaskDelegate代理中,無論任務是否成功,在完成後都會回調這個代理方法。

github

1 -        (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
2 -        {
3 -        dispatch_async(dispatch_get_main_queue(), ^{ self.progressIndicator.hidden = YES; });
4 -         }



若是error是nil,則證實下載是成功的,不然就要經過它來查詢失敗的緣由。若是下載了一部分,這個error會包含一個NSData對象,若是後面要恢復任務能夠用到。

傳輸進度
上一節結尾,你可能注意到咱們有一個進度來標示每一個任務完成度。更新進度條可能不是很容易,會有一個額外的代理來作這件事情,固然它會被調用屢次。

安全

1 -        (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten BytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
2 -        {
3 -        double currentProgress = totalBytesWritten / (double)totalBytesExpectedToWrite; dispatch_async(dispatch_get_main_queue(), ^{
4 -        self.progressIndicator.hidden = NO; self.progressIndicator.progress = currentProgress; });
5 -        }


這是NSURLSessionDownloadTaskDelegate的另外一個代理方法,咱們用來計算進度並更新進度條。

取消下載
NSURLConnection一旦發送是無法取消的。可是,咱們能夠很容易的取消掉一個NSURLSessionTask任務:

服務器

1 -        (IBAction)cancelCancellable:(id)sender
2 -        {
3 -         if(cancellableTask)
4 -        {
5 -         [cancellableTask cancel];
6 -        cancellableTask = nil;
7 -        }
8 -        }



很是容易!當取消後,會回調這個URLSession:task:didCompleteWithError:代理方法,通知你去及時更新UI。當取消一個任務後,也十分可能會再一次回調這個代理方法URLSession:downloadTask:didWriteData:BytesWritten:totalBytesExpectedToWrite: 。固然,didComplete 方法確定是最後一個回調的。

恢復下載
恢復下載也很是容易。這裏重寫了個取消方法,會生成一個NSData對象,能夠在之後用來繼續下載。若是服務器支持恢復下載,這個data對象會包含已經下載了的內容。

cookie

1 -        (IBAction)cancelCancellable:(id)sender
2 -        {
3 -        if(self.resumableTask)
4 -        {
5 -        [self.resumableTask cancelByProducingResumeData:^(NSData *resumeData)
6 -        {
7 -         partialDownload = resumeData; self.resumableTask = nil; }];
8 -        }
9 -        }



上面方法中,咱們把待恢復的數據保存到一個變量中,方便後面恢復下載使用。
當新建立一個下載任務的時候,除了使用一個新的請求,咱們也可使用待恢復的下載數據:網絡

01 if(!self.resumableTask)
02  {
03 if(partialDownload)
04 {
05 self.resumableTask = [inProcessSession downloadTaskWithResumeData:partialDownload];
06  }
07  else
08  {
09  NSString *url = @"http://url/for/image";
10 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; self.resumableTask = [inProcessSession downloadTaskWithRequest:request];
11 }
12 [self.resumableTask resume];
13  }



若是咱們有這個partialDownload這個數據對象,就能夠用它來建立一個新的任務。若是沒有,就按之前的步驟來建立任務。
記住:當使用partialDownload建立任務完成後,須要把partialDownload設置爲nil。

後臺下載
NSURLSession另外一個重要的特性:即便當應用不在前臺時,你也能夠繼續傳輸任務。固然,咱們的會話模式也要爲後臺模式:

session

1 -        (NSURLSession *)backgroundSession
2 -        {
3 -        static NSURLSession *backgroundSession = nil;
4 -        static dispatch_once_t onceToken;
5 -        dispatch_once(&onceToken, ^{
6 -        NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.shinobicontrols.BackgroundDownload.BackgroundSession"];
7 -        backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; });
8 -        return backgroundSession;
9 -         }



須要很是注意的是,經過給的後臺token,咱們只能建立一個後臺會話,因此這裏使用dispatch once block。token的目的是爲了當應用重啓後,咱們能夠經過它獲取會話。建立一個後臺會話,會啓動一個後臺傳輸守護進程,這個進程會管理數據並傳輸給咱們。即便當應用掛起或者終止,它也會繼續運行。

開啓後臺下載任務和以前同樣,全部的後臺功能都是NSURLSession本身管理的。

app

1 NSString *url = @"http://url/for/picture";
2 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; self.backgroundTask = [self.backgroundSession downloadTaskWithRequest:request]; [self.backgrounTask resume];


如今,即便你按home鍵離開應用,下載也會在後臺繼續(受開始提到的配置項控制)。

當下載完成後,你的應用將被重啓,並傳輸內容過來。
將會調用app delegate的這個方法:

1 -(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
2 {
3 self.backgroundURLSessionCompletionHandler = completionHandler;
4 }



這裏,咱們獲取內容經過completionHandler,當咱們接收下載的數據並更新UI時會調用completionHandler。咱們保存了completionHandler(注意須要copy),讓正在加載的View Controller來處理數據。當View Controller加載成功後,建立後臺會話(並設置代理)。所以以前使用的相同代理方法就會被調用。

01 -        (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
02 -        { // Save the file off as before, and set it as an image view//...
03 -        if (session == self.backgroundSession)
04 -        {
05 -        self.backgroundTask = nil;
06 -        // Get hold of the app delegate
07 -        SCAppDelegate *appDelegate = (SCAppDelegate *)[[UIApplication sharedApplication] delegate];
08 -         if(appDelegate.backgroundURLSessionCompletionHandler)
09 -         { // Need to copy the completion handlervoid (^handler)() = appDelegate.backgroundURLSessionCompletionHandler; appDelegate.backgroundURLSessionCompletionHandler = nil;
10 -         handler();
11 -         }
12 -         }
13 -         }



須要注意的幾個地方:
1.        不能用downloadTask和self.backgroundTask來比較。由於咱們不能肯定self.backgroundTask是否是已經有了,有多是應用新的一次重啓。比較session是可行的。
2.        這裏使用app delegate來獲取completion handler 的。其實,有不少方式來獲取completion handler 的。
3.        當保存完文件並顯示完成後,若是有completion handler,須要移除而後調用。這個是爲了告訴系統咱們已經完成了,能夠處理新的下載任務了。

相關文章
相關標籤/搜索