NSURLSession的Download Task用於完成下載任務,本文介紹如何建立斷點續傳的下載任務和後臺下載任務。ios
咱們直接從分析Demo入手:git
故事板以下:github
只有一個View Controller,用於建立各類下載任務,並將下載後的圖片顯示到視圖上,下載過程當中會更新下載進度。網絡
頭文件代碼以下:session
#import <UIKit/UIKit.h> 架構
@interface ViewController : UIViewController <NSURLSessionDownloadDelegate> app
/* NSURLSessions */ 異步
@property (strong, nonatomic) NSURLSession *currentSession; // 當前會話 async
@property (strong, nonatomic, readonly) NSURLSession *backgroundSession; // 後臺會話 ide
/* 下載任務 */
@property (strong, nonatomic) NSURLSessionDownloadTask *cancellableTask; // 可取消的下載任務
@property (strong, nonatomic) NSURLSessionDownloadTask *resumableTask; // 可恢復的下載任務
@property (strong, nonatomic) NSURLSessionDownloadTask *backgroundTask; // 後臺的下載任務
/* 用於可恢復的下載任務的數據 */
@property (strong, nonatomic) NSData *partialData;
/* 顯示已經下載的圖片 */
@property (weak, nonatomic) IBOutlet UIImageView *downloadedImageView;
/* 下載進度 */
@property (weak, nonatomic) IBOutlet UILabel *currentProgress_label;
@property (weak, nonatomic) IBOutlet UIProgressView *downloadingProgressView;
/* 工具欄上的按鈕 */
@property (weak, nonatomic) IBOutlet UIBarButtonItem *cancellableDownload_barButtonItem;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *resumableDownload_barButtonItem;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *backgroundDownload_barButtonItem;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *cancelTask_barButtonItem;
- (IBAction)cancellableDownload:(id)sender; // 建立可取消的下載任務
- (IBAction)resumableDownload:(id)sender; // 建立可恢復的下載任務
- (IBAction)backgroundDownload:(id)sender; // 建立後臺下載任務
- (IBAction)cancelDownloadTask:(id)sender; // 取消全部下載任務
@end
1、建立普通的下載任務
這種下載任務是能夠取消的,代碼以下:
- (IBAction)cancellableDownload:(id)sender {
if (!self.cancellableTask) {
if (!self.currentSession) {
[self createCurrentSession];
}
NSString *imageURLStr = @"http://farm6.staticflickr.com/5505/9824098016_0e28a047c2_b_d.jpg";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
self.cancellableTask = [self.currentSession downloadTaskWithRequest:request];
[self setDownloadButtonsWithEnabled:NO];
self.downloadedImageView.image = nil;
[self.cancellableTask resume];
}
}
若是當前的session爲空,首先須要建立一個session(該session使用默認配置模式,其delegate爲本身):
/* 建立當前的session */
- (void)createCurrentSession {
NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
self.currentSession = [NSURLSession sessionWithConfiguration:defaultConfig delegate:self delegateQueue:nil];
self.currentSession.sessionDescription = kCurrentSession;
}
隨後建立下載任務並啓動。
這種任務是可取消的,即下次下載又從0.0%開始:
if (self.cancellableTask) {
[self.cancellableTask cancel];
self.cancellableTask = nil;
}
2、建立可恢復的下載任務
可恢復的下載任務支持斷點續傳,也就是若是暫停當前任務,在下次再執行任務時,將從以前的下載進度中繼續進行。所以咱們首先須要一個NSData對象來保存已經下載的數據:
/* 用於可恢復的下載任務的數據 */
@property (strong, nonatomic) NSData *partialData;
執行下載任務時,若是是恢復下載,那麼就使用downloadTaskWithResumeData:方法根據partialData繼續下載。代碼以下:
- (IBAction)resumableDownload:(id)sender {
if (!self.resumableTask) {
if (!self.currentSession) {
[self createCurrentSession];
}
if (self.partialData) { // 若是是以前被暫停的任務,就從已經保存的數據恢復下載
self.resumableTask = [self.currentSession downloadTaskWithResumeData:self.partialData];
}
else { // 不然建立下載任務
NSString *imageURLStr = @"http://farm3.staticflickr.com/2846/9823925914_78cd653ac9_b_d.jpg";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
self.resumableTask = [self.currentSession downloadTaskWithRequest:request];
}
[self setDownloadButtonsWithEnabled:NO];
self.downloadedImageView.image = nil;
[self.resumableTask resume];
}
}
在取消下載任務時,要將partialData數據保存起來,並且不要調用cancel方法:
else if (self.resumableTask) {
[self.resumableTask cancelByProducingResumeData:^(NSData *resumeData) {
// 若是是可恢復的下載任務,應該先將數據保存到partialData中,注意在這裏不要調用cancel方法
self.partialData = resumeData;
self.resumableTask = nil;
}];
}
另外在恢復下載時,NSURLSessionDownloadDelegate中的如下方法將被調用:
/* 從fileOffset位移處恢復下載任務 */
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes {
NSLog(@"NSURLSessionDownloadDelegate: Resume download at %lld", fileOffset);
}
3、建立後臺下載任務
後臺下載任務,顧名思義,當程序進入後臺後,下載任務依然繼續執行。
首先建立一個後臺session單例,這裏的Session配置使用後臺配置模式,使用backgroundSessinConfiguration:方法配置時應該經過後面的參數爲該後臺進程指定一個標識符,在有多個後臺下載任務時這個標識符就起做用了。
/* 建立一個後臺session單例 */
- (NSURLSession *)backgroundSession {
static NSURLSession *backgroundSess = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:kBackgroundSessionID];
backgroundSess = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
backgroundSess.sessionDescription = kBackgroundSession;
});
return backgroundSess;
}
在建立後臺下載任務時,應該使用後臺session建立,而後resume。
- (IBAction)backgroundDownload:(id)sender {
NSString *imageURLStr = @"http://farm3.staticflickr.com/2831/9823890176_82b4165653_b_d.jpg";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
self.backgroundTask = [self.backgroundSession downloadTaskWithRequest:request];
[self setDownloadButtonsWithEnabled:NO];
self.downloadedImageView.image = nil;
[self.backgroundTask resume];
}
在程序進入後臺後,若是下載任務完成,程序委託中的對應方法將被回調:
/* 後臺下載任務完成後,程序被喚醒,該方法將被調用 */
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
NSLog(@"Application Delegate: Background download task finished");
// 設置回調的完成代碼塊
self.backgroundURLSessionCompletionHandler = completionHandler;
}
而後調用NSURLSessionDownloadDelegate中的方法:
如下是
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL*)location中的方法,該方法只有下載成功才被調用:
else if (session == self.backgroundSession) {
self.backgroundTask = nil;
AppDelegate *appDelegate = [AppDelegate sharedDelegate];
if (appDelegate.backgroundURLSessionCompletionHandler) {
// 執行回調代碼塊
void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;
appDelegate.backgroundURLSessionCompletionHandler = nil;
handler();
}
}
另外不管下載成功與否,如下方法都會被調用:
/* 完成下載任務,不管下載成功仍是失敗都調用該方法 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSLog(@"NSURLSessionDownloadDelegate: Complete task");
dispatch_async(dispatch_get_main_queue(), ^{
[self setDownloadButtonsWithEnabled:YES];
});
if (error) {
NSLog(@"下載失敗:%@", error);
[self setDownloadProgress:0.0];
self.downloadedImageView.image = nil;
}
}
取消後臺下載任務時直接cancel便可:
else if (self.backgroundTask) {
[self.backgroundTask cancel];
self.backgroundTask = nil;
}
4、NSURLSessionDownloadDelegate
爲了實現下載進度的顯示,須要在委託中的如下方法中實現:
/* 執行下載任務時有數據寫入 */
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten // 每次寫入的data字節數
totalBytesWritten:(int64_t)totalBytesWritten // 當前一共寫入的data字節數
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite // 指望收到的全部data字節數
{
// 計算當前下載進度並更新視圖
double downloadProgress = totalBytesWritten / (double)totalBytesExpectedToWrite;
[self setDownloadProgress:downloadProgress];
}
/* 根據下載進度更新視圖 */
- (void)setDownloadProgress:(double)progress {
NSString *progressStr = [NSString stringWithFormat:@"%.1f", progress * 100];
progressStr = [progressStr stringByAppendingString:@"%"];
dispatch_async(dispatch_get_main_queue(), ^{
self.downloadingProgressView.progress = progress;
self.currentProgress_label.text = progressStr;
});
}
從已經保存的數據中恢復下載任務的委託方法,fileOffset指定了恢復下載時的文件位移字節數:
/* Sent when a download has been resumed. If a download failed with an
* error, the -userInfo dictionary of the error will contain an
* NSURLSessionDownloadTaskResumeData key, whose value is the resume
* data.
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes;
只有下載成功才調用的委託方法,在該方法中應該將下載成功後的文件移動到咱們想要的目標路徑:
/* Sent when a download task that has completed a download. The delegate should
* copy or move the file at the given location to a new location as it will be
* removed when the delegate message returns. URLSession:task:didCompleteWithError: will
* still be called.
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location;
不管下載成功或失敗都會調用的方法,相似於try-catch-finally中的finally語句塊的執行。若是下載成功,那麼error參數的值爲nil,不然下載失敗,能夠經過該參數查看出錯信息:
/* Sent as the last message related to a specific task. Error may be
* nil, which implies that no error occurred and this task is complete.
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error;
後臺下載的運行結果:
啓動任務後,進入後臺:
下載完成後,控制檯將會「通知」咱們:
2014-02-05 18:30:39.767 DownloadTask[3472:70b] Application Delegate: App did become active
2014-02-05 18:30:43.734 DownloadTask[3472:70b] Application Delegate: App will resign active
2014-02-05 18:30:43.735 DownloadTask[3472:70b] Application Delegate: App did enter background
2014-02-05 18:30:45.282 DownloadTask[3472:70b] Application Delegate: Background download task finished
2014-02-05 18:30:45.285 DownloadTask[3472:4907] NSURLSessionDownloadDelegate: Finish downloading
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也已經上傳,有興趣的話能夠下載看看。