美文网首页iOS菜鸟食谱
NSURLSession实现断点续传

NSURLSession实现断点续传

作者: 坤哥lqk | 来源:发表于2015-11-21 18:50 被阅读529次

    NSURLSession学习笔记(三)Download Task

    byrootLeave a Comment

    NSURLSession的Download Task用于完成下载任务,本文介绍如何创建断点续传的下载任务和后台下载任务。

    我们直接从分析Demo入手:

    故事板如下:

    只有一个View Controller,用于创建各种下载任务,并将下载后的图片显示到视图上,下载过程中会更新下载进度。

    头文件代码如下:

    [objc]view plaincopy

    #import 

    @interface ViewController : UIViewController 

    /* NSURLSessions */

    @property (strong,nonatomic)NSURLSession *currentSession;// 当前会话

    @property (strong,nonatomic,readonly)NSURLSession *backgroundSession;// 后台会话

    /* 下载任务 */

    @property (strong,nonatomic)NSURLSessionDownloadTask *cancellableTask;// 可取消的下载任务

    @property (strong,nonatomic)NSURLSessionDownloadTask *resumableTask;// 可恢复的下载任务

    @property (strong,nonatomic)NSURLSessionDownloadTask *backgroundTask;// 后台的下载任务

    /* 用于可恢复的下载任务的数据 */

    @property (strong,nonatomic)NSData *partialData;

    /* 显示已经下载的图片 */

    @property (weak,nonatomic) IBOutletUIImageView *downloadedImageView;

    /* 下载进度 */

    @property (weak,nonatomic) IBOutletUILabel *currentProgress_label;

    @property (weak,nonatomic) IBOutletUIProgressView *downloadingProgressView;

    /* 工具栏上的按钮 */

    @property (weak,nonatomic) IBOutletUIBarButtonItem *cancellableDownload_barButtonItem;

    @property (weak,nonatomic) IBOutletUIBarButtonItem *resumableDownload_barButtonItem;

    @property (weak,nonatomic) IBOutletUIBarButtonItem *backgroundDownload_barButtonItem;

    @property (weak,nonatomic) IBOutletUIBarButtonItem *cancelTask_barButtonItem;

    - (IBAction)cancellableDownload:(id)sender;// 创建可取消的下载任务

    - (IBAction)resumableDownload:(id)sender;// 创建可恢复的下载任务

    - (IBAction)backgroundDownload:(id)sender;// 创建后台下载任务

    - (IBAction)cancelDownloadTask:(id)sender;// 取消所有下载任务

    @end

    一、创建普通的下载任务

    这种下载任务是可以取消的,代码如下:

    [objc]view plaincopy

    - (IBAction)cancellableDownload:(id)sender {

    if (!self.cancellableTask) {

    if (!self.currentSession) {

    [selfcreateCurrentSession];

    }

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

    NSURLRequest *request = [NSURLRequestrequestWithURL:[NSURLURLWithString:imageURLStr]];

    self.cancellableTask = [self.currentSessiondownloadTaskWithRequest:request];

    [selfsetDownloadButtonsWithEnabled:NO];

    self.downloadedImageView.image =nil;

    [self.cancellableTaskresume];

    }

    }

    如果当前的session为空,首先需要创建一个session(该session使用默认配置模式,其delegate为自己):

    [objc]view plaincopy

    /* 创建当前的session */

    - (void)createCurrentSession {

    NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfigurationdefaultSessionConfiguration];

    self.currentSession = [NSURLSessionsessionWithConfiguration:defaultConfigdelegate:selfdelegateQueue:nil];

    self.currentSession.sessionDescription = kCurrentSession;

    }

    随后创建下载任务并启动。

    这种任务是可取消的,即下次下载又从0.0%开始:

    [objc]view plaincopy

    if (self.cancellableTask) {

    [self.cancellableTaskcancel];

    self.cancellableTask =nil;

    }

    二、创建可恢复的下载任务

    可恢复的下载任务支持断点续传,也就是如果暂停当前任务,在下次再执行任务时,将从之前的下载进度中继续进行。因此我们首先需要一个NSData对象来保存已经下载的数据:

    [objc]view plaincopy

    /* 用于可恢复的下载任务的数据 */

    @property (strong,nonatomic)NSData *partialData;

    执行下载任务时,如果是恢复下载,那么就使用downloadTaskWithResumeData:方法根据partialData继续下载。代码如下:

    [objc]view plaincopy

    - (IBAction)resumableDownload:(id)sender {

    if (!self.resumableTask) {

    if (!self.currentSession) {

    [selfcreateCurrentSession];

    }

    if (self.partialData) {// 如果是之前被暂停的任务,就从已经保存的数据恢复下载

    self.resumableTask = [self.currentSessiondownloadTaskWithResumeData:self.partialData];

    }

    else {// 否则创建下载任务

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

    NSURLRequest *request = [NSURLRequestrequestWithURL:[NSURLURLWithString:imageURLStr]];

    self.resumableTask = [self.currentSessiondownloadTaskWithRequest:request];

    }

    [selfsetDownloadButtonsWithEnabled:NO];

    self.downloadedImageView.image =nil;

    [self.resumableTaskresume];

    }

    }

    在取消下载任务时,要将partialData数据保存起来,而且不要调用cancel方法:

    [objc]view plaincopy

    elseif (self.resumableTask) {

    [self.resumableTaskcancelByProducingResumeData:^(NSData *resumeData) {

    // 如果是可恢复的下载任务,应该先将数据保存到partialData中,注意在这里不要调用cancel方法

    self.partialData = resumeData;

    self.resumableTask =nil;

    }];

    }

    另外在恢复下载时,NSURLSessionDownloadDelegate中的以下方法将被调用:

    [objc]view plaincopy

    /* 从fileOffset位移处恢复下载任务 */

    - (void)URLSession:(NSURLSession *)session

    downloadTask:(NSURLSessionDownloadTask *)downloadTask

    didResumeAtOffset:(int64_t)fileOffset

    expectedTotalBytes:(int64_t)expectedTotalBytes {

    NSLog(@”NSURLSessionDownloadDelegate: Resume download at %lld”, fileOffset);

    }

    三、创建后台下载任务

    后台下载任务,顾名思义,当程序进入后台后,下载任务依然继续执行。

    首先创建一个后台session单例,这里的Session配置使用后台配置模式,使用backgroundSessinConfiguration:方法配置时应该通过后面的参数为该后台进程指定一个标识符,在有多个后台下载任务时这个标识符就起作用了。

    [objc]view plaincopy

    /* 创建一个后台session单例 */

    - (NSURLSession *)backgroundSession {

    staticNSURLSession *backgroundSess =nil;

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

    NSURLSessionConfiguration *config = [NSURLSessionConfigurationbackgroundSessionConfiguration:kBackgroundSessionID];

    backgroundSess = [NSURLSessionsessionWithConfiguration:configdelegate:selfdelegateQueue:nil];

    backgroundSess.sessionDescription = kBackgroundSession;

    });

    return backgroundSess;

    }

    在创建后台下载任务时,应该使用后台session创建,然后resume。

    [objc]view plaincopy

    - (IBAction)backgroundDownload:(id)sender {

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

    NSURLRequest *request = [NSURLRequestrequestWithURL:[NSURLURLWithString:imageURLStr]];

    self.backgroundTask = [self.backgroundSessiondownloadTaskWithRequest:request];

    [selfsetDownloadButtonsWithEnabled:NO];

    self.downloadedImageView.image =nil;

    [self.backgroundTaskresume];

    }

    在程序进入后台后,如果下载任务完成,程序委托中的对应方法将被回调:

    [objc]view plaincopy

    /* 后台下载任务完成后,程序被唤醒,该方法将被调用 */

    - (void)application:(UIApplication *)applicationhandleEventsForBackgroundURLSession:(NSString *)identifiercompletionHandler:(void (^)())completionHandler {

    NSLog(@”Application Delegate: Background download task finished”);

    // 设置回调的完成代码块

    self.backgroundURLSessionCompletionHandler = completionHandler;

    }

    然后调用NSURLSessionDownloadDelegate中的方法:

    以下是

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

    [objc]view plaincopy

    elseif (session ==self.backgroundSession) {

    self.backgroundTask =nil;

    AppDelegate *appDelegate = [AppDelegatesharedDelegate];

    if (appDelegate.backgroundURLSessionCompletionHandler) {

    // 执行回调代码块

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

    appDelegate.backgroundURLSessionCompletionHandler =nil;

    handler();

    }

    }

    另外无论下载成功与否,以下方法都会被调用:

    [objc]view plaincopy

    /* 完成下载任务,无论下载成功还是失败都调用该方法 */

    - (void)URLSession:(NSURLSession *)sessiontask:(NSURLSessionTask *)taskdidCompleteWithError:(NSError *)error {

    NSLog(@”NSURLSessionDownloadDelegate: Complete task”);

    dispatch_async(dispatch_get_main_queue(), ^{

    [selfsetDownloadButtonsWithEnabled:YES];

    });

    if (error) {

    NSLog(@”下载失败:%@”, error);

    [selfsetDownloadProgress:0.0];

    self.downloadedImageView.image =nil;

    }

    }

    取消后台下载任务时直接cancel即可:

    [objc]view plaincopy

    elseif (self.backgroundTask) {

    [self.backgroundTaskcancel];

    self.backgroundTask =nil;

    }

    四、NSURLSessionDownloadDelegate

    为了实现下载进度的显示,需要在委托中的以下方法中实现:

    [objc]view plaincopy

    /* 执行下载任务时有数据写入 */

    - (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;

    [selfsetDownloadProgress:downloadProgress];

    }

    /* 根据下载进度更新视图 */

    - (void)setDownloadProgress:(double)progress {

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

    progressStr = [progressStrstringByAppendingString:@"%"];

    dispatch_async(dispatch_get_main_queue(), ^{

    self.downloadingProgressView.progress = progress;

    self.currentProgress_label.text = progressStr;

    });

    }

    从已经保存的数据中恢复下载任务的委托方法,fileOffset指定了恢复下载时的文件位移字节数:

    [objc]view plaincopy

    /* 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 *)sessiondownloadTask:(NSURLSessionDownloadTask *)downloadTask

    didResumeAtOffset:(int64_t)fileOffset

    expectedTotalBytes:(int64_t)expectedTotalBytes;

    只有下载成功才调用的委托方法,在该方法中应该将下载成功后的文件移动到我们想要的目标路径:

    [objc]view plaincopy

    /* 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 *)sessiondownloadTask:(NSURLSessionDownloadTask *)downloadTask

    didFinishDownloadingToURL:(NSURL *)location;

    无论下载成功或失败都会调用的方法,类似于try-catch-finally中的finally语句块的执行。如果下载成功,那么error参数的值为nil,否则下载失败,可以通过该参数查看出错信息:

    [objc]view plaincopy

    /* 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 *)sessiontask:(NSURLSessionTask *)task

    didCompleteWithError:(NSError *)error;

    后台下载的运行结果:

    启动任务后,进入后台:

    下载完成后,控制台将会“通知”我们:

    [objc]view plaincopy

    相关文章

      网友评论

        本文标题:NSURLSession实现断点续传

        本文链接:https://www.haomeiwen.com/subject/drzlhttx.html