NSURLSession學習筆記(三)Download Task

NSURLSession的Download Task用於完成下載任務,本文介紹如何建立斷點續傳的下載任務和後臺下載任務。ios


咱們直接從分析Demo入手:git

故事板以下:github



只有一個View Controller,用於建立各類下載任務,並將下載後的圖片顯示到視圖上,下載過程當中會更新下載進度。網絡

頭文件代碼以下:session


  1. #import <UIKit/UIKit.h>  架構

  2.   

  3. @interface  ViewController : UIViewController <NSURLSessionDownloadDelegate>  app

  4.   

  5. /* NSURLSessions */  異步

  6. @property (strongnonatomic)           NSURLSession *currentSession;    // 當前會話  async

  7. @property (strongnonatomicreadonlyNSURLSession *backgroundSession; // 後臺會話  ide

  8.   

  9. /* 下載任務 */  

  10. @property (strongnonatomicNSURLSessionDownloadTask *cancellableTask; // 可取消的下載任務  

  11. @property (strongnonatomicNSURLSessionDownloadTask *resumableTask;   // 可恢復的下載任務  

  12. @property (strongnonatomicNSURLSessionDownloadTask *backgroundTask;  // 後臺的下載任務  

  13.   

  14. /* 用於可恢復的下載任務的數據 */  

  15. @property (strongnonatomicNSData *partialData;  

  16.   

  17. /* 顯示已經下載的圖片 */  

  18. @property (weak, nonatomic) IBOutlet UIImageView *downloadedImageView;  

  19.   

  20. /* 下載進度 */  

  21. @property (weak, nonatomic) IBOutlet UILabel *currentProgress_label;  

  22. @property (weak, nonatomic) IBOutlet UIProgressView *downloadingProgressView;  

  23.   

  24. /* 工具欄上的按鈕 */  

  25. @property (weak, nonatomic) IBOutlet UIBarButtonItem *cancellableDownload_barButtonItem;  

  26. @property (weak, nonatomic) IBOutlet UIBarButtonItem *resumableDownload_barButtonItem;  

  27. @property (weak, nonatomic) IBOutlet UIBarButtonItem *backgroundDownload_barButtonItem;  

  28. @property (weak, nonatomic) IBOutlet UIBarButtonItem *cancelTask_barButtonItem;  

  29.   

  30. - (IBAction)cancellableDownload:(id)sender; // 建立可取消的下載任務  

  31. - (IBAction)resumableDownload:(id)sender;   // 建立可恢復的下載任務  

  32. - (IBAction)backgroundDownload:(id)sender;  // 建立後臺下載任務  

  33. - (IBAction)cancelDownloadTask:(id)sender;  // 取消全部下載任務  

  34.   

  35. @end  



1、建立普通的下載任務

這種下載任務是能夠取消的,代碼以下:

  1. - (IBAction)cancellableDownload:(id)sender {  

  2.     if (!self.cancellableTask) {  

  3.         if (!self.currentSession) {  

  4.             [self createCurrentSession];  

  5.         }  

  6.           

  7.         NSString *imageURLStr = @"http://farm6.staticflickr.com/5505/9824098016_0e28a047c2_b_d.jpg";  

  8.         NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];  

  9.         self.cancellableTask = [self.currentSession downloadTaskWithRequest:request];  

  10.           

  11.         [self setDownloadButtonsWithEnabled:NO];  

  12.         self.downloadedImageView.image = nil;  

  13.           

  14.         [self.cancellableTask resume];  

  15.     }  

  16. }  


若是當前的session爲空,首先須要建立一個session(該session使用默認配置模式,其delegate爲本身):

  1. /* 建立當前的session */  

  2. - (void)createCurrentSession {  

  3.     NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfiguration defaultSessionConfiguration];  

  4.     self.currentSession = [NSURLSession sessionWithConfiguration:defaultConfig delegate:self delegateQueue:nil];  

  5.     self.currentSession.sessionDescription = kCurrentSession;  

  6. }  


隨後建立下載任務並啓動。

這種任務是可取消的,即下次下載又從0.0%開始:

  1. if (self.cancellableTask) {  

  2.     [self.cancellableTask cancel];  

  3.     self.cancellableTask = nil;  

  4. }  



2、建立可恢復的下載任務

可恢復的下載任務支持斷點續傳,也就是若是暫停當前任務,在下次再執行任務時,將從以前的下載進度中繼續進行。所以咱們首先須要一個NSData對象來保存已經下載的數據:

  1. /* 用於可恢復的下載任務的數據 */  

  2. @property (strongnonatomicNSData *partialData;  


執行下載任務時,若是是恢復下載,那麼就使用downloadTaskWithResumeData:方法根據partialData繼續下載。代碼以下:

  1. - (IBAction)resumableDownload:(id)sender {  

  2.     if (!self.resumableTask) {  

  3.         if (!self.currentSession) {  

  4.             [self createCurrentSession];  

  5.         }  

  6.           

  7.         if (self.partialData) { // 若是是以前被暫停的任務,就從已經保存的數據恢復下載  

  8.             self.resumableTask = [self.currentSession downloadTaskWithResumeData:self.partialData];  

  9.         }  

  10.         else { // 不然建立下載任務  

  11.             NSString *imageURLStr = @"http://farm3.staticflickr.com/2846/9823925914_78cd653ac9_b_d.jpg";  

  12.             NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];  

  13.             self.resumableTask = [self.currentSession downloadTaskWithRequest:request];  

  14.         }  

  15.           

  16.         [self setDownloadButtonsWithEnabled:NO];  

  17.         self.downloadedImageView.image = nil;  

  18.           

  19.         [self.resumableTask resume];  

  20.     }  

  21. }  


在取消下載任務時,要將partialData數據保存起來,並且不要調用cancel方法:

  1. else if (self.resumableTask) {  

  2.     [self.resumableTask cancelByProducingResumeData:^(NSData *resumeData) {  

  3.         // 若是是可恢復的下載任務,應該先將數據保存到partialData中,注意在這裏不要調用cancel方法  

  4.         self.partialData = resumeData;  

  5.         self.resumableTask = nil;  

  6.     }];  

  7. }  


另外在恢復下載時,NSURLSessionDownloadDelegate中的如下方法將被調用:


  1. /* 從fileOffset位移處恢復下載任務 */  

  2. - (void)URLSession:(NSURLSession *)session  

  3.       downloadTask:(NSURLSessionDownloadTask *)downloadTask  

  4.  didResumeAtOffset:(int64_t)fileOffset  

  5. expectedTotalBytes:(int64_t)expectedTotalBytes {  

  6.     NSLog(@"NSURLSessionDownloadDelegate: Resume download at %lld", fileOffset);  

  7. }  



3、建立後臺下載任務

後臺下載任務,顧名思義,當程序進入後臺後,下載任務依然繼續執行。

首先建立一個後臺session單例,這裏的Session配置使用後臺配置模式,使用backgroundSessinConfiguration:方法配置時應該經過後面的參數爲該後臺進程指定一個標識符,在有多個後臺下載任務時這個標識符就起做用了。

  1. /* 建立一個後臺session單例 */  

  2. - (NSURLSession *)backgroundSession {  

  3.     static NSURLSession *backgroundSess = nil;  

  4.     static dispatch_once_t onceToken;  

  5.     dispatch_once(&onceToken, ^{  

  6.         NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:kBackgroundSessionID];  

  7.         backgroundSess = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];  

  8.         backgroundSess.sessionDescription = kBackgroundSession;  

  9.     });  

  10.       

  11.     return backgroundSess;  

  12. }  


在建立後臺下載任務時,應該使用後臺session建立,而後resume。

  1. - (IBAction)backgroundDownload:(id)sender {  

  2.     NSString *imageURLStr = @"http://farm3.staticflickr.com/2831/9823890176_82b4165653_b_d.jpg";  

  3.     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];  

  4.     self.backgroundTask = [self.backgroundSession downloadTaskWithRequest:request];  

  5.       

  6.     [self setDownloadButtonsWithEnabled:NO];  

  7.     self.downloadedImageView.image = nil;  

  8.       

  9.     [self.backgroundTask resume];  

  10. }  


在程序進入後臺後,若是下載任務完成,程序委託中的對應方法將被回調:

  1. /* 後臺下載任務完成後,程序被喚醒,該方法將被調用 */  

  2. - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {  

  3.     NSLog(@"Application Delegate: Background download task finished");  

  4.       

  5.     // 設置回調的完成代碼塊  

  6.     self.backgroundURLSessionCompletionHandler = completionHandler;  

  7. }  


而後調用NSURLSessionDownloadDelegate中的方法:

如下是

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL*)location中的方法,該方法只有下載成功才被調用:

  1. else if (session == self.backgroundSession) {  

  2.     self.backgroundTask = nil;  

  3.     AppDelegate *appDelegate = [AppDelegate sharedDelegate];  

  4.     if (appDelegate.backgroundURLSessionCompletionHandler) {  

  5.         // 執行回調代碼塊  

  6.         void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;  

  7.         appDelegate.backgroundURLSessionCompletionHandler = nil;  

  8.         handler();  

  9.     }  

  10. }  


另外不管下載成功與否,如下方法都會被調用:

  1. /* 完成下載任務,不管下載成功仍是失敗都調用該方法 */  

  2. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {  

  3.     NSLog(@"NSURLSessionDownloadDelegate: Complete task");  

  4.       

  5.     dispatch_async(dispatch_get_main_queue(), ^{  

  6.         [self setDownloadButtonsWithEnabled:YES];  

  7.     });  

  8.       

  9.     if (error) {  

  10.         NSLog(@"下載失敗:%@", error);  

  11.         [self setDownloadProgress:0.0];  

  12.         self.downloadedImageView.image = nil;  

  13.     }  

  14. }  


取消後臺下載任務時直接cancel便可:

  1. else if (self.backgroundTask) {  

  2.     [self.backgroundTask cancel];  

  3.     self.backgroundTask = nil;  

  4. }  



4、NSURLSessionDownloadDelegate

爲了實現下載進度的顯示,須要在委託中的如下方法中實現:

  1. /* 執行下載任務時有數據寫入 */  

  2. - (void)URLSession:(NSURLSession *)session  

  3.       downloadTask:(NSURLSessionDownloadTask *)downloadTask  

  4.       didWriteData:(int64_t)bytesWritten // 每次寫入的data字節數  

  5.  totalBytesWritten:(int64_t)totalBytesWritten // 當前一共寫入的data字節數  

  6. totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite // 指望收到的全部data字節數  

  7. {  

  8.     // 計算當前下載進度並更新視圖  

  9.     double downloadProgress = totalBytesWritten / (double)totalBytesExpectedToWrite;  

  10.     [self setDownloadProgress:downloadProgress];  

  11. }  

  12.   

  13. /* 根據下載進度更新視圖 */  

  14. - (void)setDownloadProgress:(double)progress {  

  15.     NSString *progressStr = [NSString stringWithFormat:@"%.1f"progress * 100];  

  16.     progressStr = [progressStr stringByAppendingString:@"%"];  

  17.       

  18.     dispatch_async(dispatch_get_main_queue(), ^{  

  19.         self.downloadingProgressView.progress = progress;  

  20.         self.currentProgress_label.text = progressStr;  

  21.     });  

  22. }  



從已經保存的數據中恢復下載任務的委託方法,fileOffset指定了恢復下載時的文件位移字節數:

  1. /* Sent when a download has been resumed. If a download failed with an 

  2.  * error, the -userInfo dictionary of the error will contain an 

  3.  * NSURLSessionDownloadTaskResumeData key, whose value is the resume 

  4.  * data.  

  5.  */  

  6. - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask  

  7.                                       didResumeAtOffset:(int64_t)fileOffset  

  8.                                      expectedTotalBytes:(int64_t)expectedTotalBytes;  



只有下載成功才調用的委託方法,在該方法中應該將下載成功後的文件移動到咱們想要的目標路徑:

  1. /* Sent when a download task that has completed a download.  The delegate should  

  2.  * copy or move the file at the given location to a new location as it will be  

  3.  * removed when the delegate message returns. URLSession:task:didCompleteWithError: will 

  4.  * still be called. 

  5.  */  

  6. - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask  

  7.                               didFinishDownloadingToURL:(NSURL *)location;  



不管下載成功或失敗都會調用的方法,相似於try-catch-finally中的finally語句塊的執行。若是下載成功,那麼error參數的值爲nil,不然下載失敗,能夠經過該參數查看出錯信息:

  1. /* Sent as the last message related to a specific task.  Error may be 

  2.  * nil, which implies that no error occurred and this task is complete.  

  3.  */  

  4. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task  

  5.                            didCompleteWithError:(NSError *)error;  




後臺下載的運行結果:

啓動任務後,進入後臺:



下載完成後,控制檯將會「通知」咱們:

  1. 2014-02-05 18:30:39.767 DownloadTask[3472:70b] Application Delegate: App did become active  

  2. 2014-02-05 18:30:43.734 DownloadTask[3472:70b] Application Delegate: App will resign active  

  3. 2014-02-05 18:30:43.735 DownloadTask[3472:70b] Application Delegate: App did enter background  

  4. 2014-02-05 18:30:45.282 DownloadTask[3472:70b] Application Delegate: Background download task finished  

  5. 2014-02-05 18:30:45.285 DownloadTask[3472:4907] NSURLSessionDownloadDelegate: Finish downloading  

  6. 2014-02-05 18:30:45.301 DownloadTask[3472:4907] NSURLSessionDownloadDelegate: Complete task  


再次啓動程序,能夠看到加載好的頁面:



能夠看到,經過後臺下載讓咱們的程序更加異步地運行。NSURLSession封裝了對應的接口,讓咱們要執行的任務更加專門化,這個新的網絡架構的功能真的很強大。


本文的Demo基於https://github.com/ShinobiControls/iOS7-day-by-day改寫,內容基本一致。

原來的Demo也有一篇博客對應:iOS7 Day-by-Day :: Day 1 :: NSURLSession

本文的Demo也已經上傳,有興趣的話能夠下載看看。

相關文章
相關標籤/搜索