zoukankan      html  css  js  c++  java
  • ios断点续传:NSURLSession和NSURLSessionDataTask实现

    苹果提供的NSURLSessionDownloadTask虽然能实现断点续传,但是有些情况是无法处理的,比如程序强制退出或没有调用

    cancelByProducingResumeData取消方法,这时就无法断点续传了。

    使用NSURLSession和NSURLSessionDataTask实现断点续传的过程是:

    1、配置NSMutableURLRequest对象的Range请求头字段信息

    2、创建使用代理的NSURLSession对象

    3、使用NSURLSession对象和NSMutableURLRequest对象创建NSURLSessionDataTask对象,启动任务。

    4、在NSURLSessionDataDelegate的didReceiveData方法中追加获取下载数据到目标文件。

    下面是具体实现,封装了一个续传管理器。可以直接拷贝到你的工程里,也可以参考我提供的DEMO:http://pan.baidu.com/s/1c0BHToW

    //
    
    //  MQLResumeManager.h
    
    //
    
    //  Created by MQL on 15/10/21.
    
    //  Copyright © 2015年. All rights reserved.
    
    //
    
     
    
    #import <Foundation/Foundation.h>
    
     
    
    @interface MQLResumeManager : NSObject
    
     
    
    /**
    
     *  创建断点续传管理对象,启动下载请求
    
     *
    
     *  @param url          文件资源地址
    
     *  @param targetPath   文件存放路径
    
     *  @param success      文件下载成功的回调块
    
     *  @param failure      文件下载失败的回调块
    
     *  @param progress     文件下载进度的回调块
    
     *
    
     *  @return 断点续传管理对象
    
     *
    
     */
    
    +(MQLResumeManager*)resumeManagerWithURL:(NSURL*)url
    
                                    targetPath:(NSString*)targetPath
    
                                    success:(void (^)())success
    
                                    failure:(void (^)(NSError*error))failure
    
                                   progress:(void (^)(longlongtotalReceivedContentLength,longlong totalContentLength))progress;
    
     
    
    /**
    
     *  启动断点续传下载请求
    
     */
    
    -(void)start;
    
     
    
    /**
    
     *  取消断点续传下载请求
    
     */
    
    -(void)cancel;
    
     
    
     
    
    @end
      1 //
      2 
      3 //  MQLResumeManager.m
      4 
      5 //
      6 
      7 //  Created by MQL on 15/10/21.
      8 
      9 //  Copyright © 2015年. All rights reserved.
     10 
     11 //
     12 
     13  
     14 
     15 #import "MQLResumeManager.h"
     16 
     17  
     18 
     19 typedef void (^completionBlock)();
     20 
     21 typedef void (^progressBlock)();
     22 
     23  
     24 
     25 @interface MQLResumeManager ()<NSURLSessionDelegate,NSURLSessionTaskDelegate>
     26 
     27  
     28 
     29 @property (nonatomic,strong)NSURLSession *session;   //注意一个session只能有一个请求任务
     30 
     31 @property(nonatomic,readwrite,retain)NSError *error;//请求出错
     32 
     33 @property(nonatomic,readwrite,copy)completionBlockcompletionBlock;
     34 
     35 @property(nonatomic,readwrite,copy)progressBlock progressBlock;
     36 
     37  
     38 
     39 @property (nonatomic,strong)NSURL *url;          //文件资源地址
     40 
     41 @property (nonatomic,strong)NSString *targetPath;//文件存放路径
     42 
     43 @property longlong totalContentLength;            //文件总大小
     44 
     45 @property longlong totalReceivedContentLength;    //已下载大小
     46 
     47  
     48 
     49 /**
     50 
     51  *  设置成功、失败回调block
     52 
     53  *
     54 
     55  *  @param success 成功回调block
     56 
     57  *  @param failure 失败回调block
     58 
     59  */
     60 
     61 - (void)setCompletionBlockWithSuccess:(void (^)())success
     62 
     63                               failure:(void (^)(NSError*error))failure;
     64 
     65  
     66 
     67 /**
     68 
     69  *  设置进度回调block
     70 
     71  *
     72 
     73  *  @param progress
     74 
     75  */
     76 
     77 -(void)setProgressBlockWithProgress:(void (^)(longlongtotalReceivedContentLength,longlong totalContentLength))progress;
     78 
     79  
     80 
     81 /**
     82 
     83  *  获取文件大小
     84 
     85  *  @param path 文件路径
     86 
     87  *  @return 文件大小
     88 
     89  *
     90 
     91  */
     92 
     93 - (long long)fileSizeForPath:(NSString *)path;
     94 
     95  
     96 
     97 @end
     98 
     99  
    100 
    101 @implementation MQLResumeManager
    102 
    103  
    104 
    105 /**
    106 
    107  *  设置成功、失败回调block
    108 
    109  *
    110 
    111  *  @param success 成功回调block
    112 
    113  *  @param failure 失败回调block
    114 
    115  */
    116 
    117 - (void)setCompletionBlockWithSuccess:(void (^)())success
    118 
    119                               failure:(void (^)(NSError*error))failure{
    120 
    121     
    122 
    123     __weak typeof(self) weakSelf =self;
    124 
    125     self.completionBlock = ^ {
    126 
    127         
    128 
    129         dispatch_async(dispatch_get_main_queue(), ^{
    130 
    131             
    132 
    133             if (weakSelf.error) {
    134 
    135                 if (failure) {
    136 
    137                     failure(weakSelf.error);
    138 
    139                 }
    140 
    141             } else {
    142 
    143                 if (success) {
    144 
    145                     success();
    146 
    147                 }
    148 
    149             }
    150 
    151             
    152 
    153         });
    154 
    155     };
    156 
    157 }
    158 
    159  
    160 
    161 /**
    162 
    163  *  设置进度回调block
    164 
    165  *
    166 
    167  *  @param progress
    168 
    169  */
    170 
    171 -(void)setProgressBlockWithProgress:(void (^)(longlongtotalReceivedContentLength,longlong totalContentLength))progress{
    172 
    173     
    174 
    175     __weak typeof(self) weakSelf =self;
    176 
    177     self.progressBlock = ^{
    178 
    179         
    180 
    181         dispatch_async(dispatch_get_main_queue(), ^{
    182 
    183             
    184 
    185             progress(weakSelf.totalReceivedContentLength, weakSelf.totalContentLength);
    186 
    187         });
    188 
    189     };
    190 
    191 }
    192 
    193  
    194 
    195 /**
    196 
    197  *  获取文件大小
    198 
    199  *  @param path 文件路径
    200 
    201  *  @return 文件大小
    202 
    203  *
    204 
    205  */
    206 
    207 - (long long)fileSizeForPath:(NSString *)path {
    208 
    209     
    210 
    211     long long fileSize =0;
    212 
    213     NSFileManager *fileManager = [NSFileManagernew];// not thread safe
    214 
    215     if ([fileManager fileExistsAtPath:path]) {
    216 
    217         NSError *error = nil;
    218 
    219         NSDictionary *fileDict = [fileManagerattributesOfItemAtPath:path error:&error];
    220 
    221         if (!error && fileDict) {
    222 
    223             fileSize = [fileDict fileSize];
    224 
    225         }
    226 
    227     }
    228 
    229     return fileSize;
    230 
    231 }
    232 
    233  
    234 
    235 /**
    236 
    237  *  创建断点续传管理对象,启动下载请求
    238 
    239  *
    240 
    241  *  @param url          文件资源地址
    242 
    243  *  @param targetPath   文件存放路径
    244 
    245  *  @param success      文件下载成功的回调块
    246 
    247  *  @param failure      文件下载失败的回调块
    248 
    249  *  @param progress     文件下载进度的回调块
    250 
    251  *
    252 
    253  *  @return 断点续传管理对象
    254 
    255  *
    256 
    257  */
    258 
    259 +(MQLResumeManager*)resumeManagerWithURL:(NSURL*)url
    260 
    261                               targetPath:(NSString*)targetPath
    262 
    263                                  success:(void (^)())success
    264 
    265                                  failure:(void (^)(NSError*error))failure
    266 
    267                                 progress:(void (^)(longlongtotalReceivedContentLength,longlong totalContentLength))progress{
    268 
    269     
    270 
    271     MQLResumeManager *manager = [[MQLResumeManageralloc]init];
    272 
    273     
    274 
    275     manager.url = url;
    276 
    277     manager.targetPath = targetPath;
    278 
    279     [managersetCompletionBlockWithSuccess:successfailure:failure];
    280 
    281     [manager setProgressBlockWithProgress:progress];
    282 
    283     
    284 
    285     manager.totalContentLength =0;
    286 
    287     manager.totalReceivedContentLength =0;
    288 
    289     
    290 
    291     return manager;
    292 
    293 }
    294 
    295  
    296 
    297 /**
    298 
    299  *  启动断点续传下载请求
    300 
    301  */
    302 
    303 -(void)start{
    304 
    305     
    306 
    307     NSMutableURLRequest *request = [[NSMutableURLRequestalloc]initWithURL:self.url];
    308 
    309     
    310 
    311     longlong downloadedBytes =self.totalReceivedContentLength = [selffileSizeForPath:self.targetPath];
    312 
    313     if (downloadedBytes > 0) {
    314 
    315         
    316 
    317         NSString *requestRange = [NSStringstringWithFormat:@"bytes=%llu-", downloadedBytes];
    318 
    319         [request setValue:requestRangeforHTTPHeaderField:@"Range"];
    320 
    321     }else{
    322 
    323         
    324 
    325         int fileDescriptor =open([self.targetPathUTF8String],O_CREAT |O_EXCL |O_RDWR,0666);
    326 
    327         if (fileDescriptor > 0) {
    328 
    329             close(fileDescriptor);
    330 
    331         }
    332 
    333     }
    334 
    335     
    336 
    337     NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfigurationdefaultSessionConfiguration];
    338 
    339     NSOperationQueue *queue = [[NSOperationQueuealloc]init];
    340 
    341     self.session = [NSURLSessionsessionWithConfiguration:sessionConfigurationdelegate:selfdelegateQueue:queue];
    342 
    343     
    344 
    345     NSURLSessionDataTask *dataTask = [self.sessiondataTaskWithRequest:request];
    346 
    347     [dataTask resume];
    348 
    349 }
    350 
    351  
    352 
    353 /**
    354 
    355  *  取消断点续传下载请求
    356 
    357  */
    358 
    359 -(void)cancel{
    360 
    361     
    362 
    363     if (self.session) {
    364 
    365         
    366 
    367         [self.sessioninvalidateAndCancel];
    368 
    369         self.session =nil;
    370 
    371     }
    372 
    373 }
    374 
    375  
    376 
    377 #pragma mark -- NSURLSessionDelegate
    378 
    379 /* The last message a session delegate receives.  A session will only become
    380 
    381  * invalid because of a systemic error or when it has been
    382 
    383  * explicitly invalidated, in which case the error parameter will be nil.
    384 
    385  */
    386 
    387 - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullableNSError *)error{
    388 
    389     
    390 
    391     NSLog(@"didBecomeInvalidWithError");
    392 
    393 }
    394 
    395  
    396 
    397 #pragma mark -- NSURLSessionTaskDelegate
    398 
    399 /* Sent as the last message related to a specific task.  Error may be
    400 
    401  * nil, which implies that no error occurred and this task is complete.
    402 
    403  */
    404 
    405 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask*)task
    406 
    407 didCompleteWithError:(nullable NSError *)error{
    408 
    409     
    410 
    411     NSLog(@"didCompleteWithError");
    412 
    413     
    414 
    415     if (error == nil &&self.error ==nil) {
    416 
    417         
    418 
    419         self.completionBlock();
    420 
    421         
    422 
    423     }else if (error !=nil){
    424 
    425         
    426 
    427         if (error.code != -999) {
    428 
    429             
    430 
    431             self.error = error;
    432 
    433             self.completionBlock();
    434 
    435         }
    436 
    437         
    438 
    439     }else if (self.error !=nil){
    440 
    441         
    442 
    443         self.completionBlock();
    444 
    445     }
    446 
    447     
    448 
    449     
    450 
    451 }
    452 
    453  
    454 
    455 #pragma mark -- NSURLSessionDataDelegate
    456 
    457 /* Sent when data is available for the delegate to consume.  It is
    458 
    459  * assumed that the delegate will retain and not copy the data.  As
    460 
    461  * the data may be discontiguous, you should use
    462 
    463  * [NSData enumerateByteRangesUsingBlock:] to access it.
    464 
    465  */
    466 
    467 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    468 
    469     didReceiveData:(NSData *)data{
    470 
    471     
    472 
    473     //根据status code的不同,做相应的处理
    474 
    475     NSHTTPURLResponse *response = (NSHTTPURLResponse*)dataTask.response;
    476 
    477     if (response.statusCode ==200) {
    478 
    479         
    480 
    481         self.totalContentLength = dataTask.countOfBytesExpectedToReceive;
    482 
    483         
    484 
    485     }else if (response.statusCode ==206){
    486 
    487         
    488 
    489         NSString *contentRange = [response.allHeaderFieldsvalueForKey:@"Content-Range"];
    490 
    491         if ([contentRange hasPrefix:@"bytes"]) {
    492 
    493             NSArray *bytes = [contentRangecomponentsSeparatedByCharactersInSet:[NSCharacterSetcharacterSetWithCharactersInString:@" -/"]];
    494 
    495             if ([bytes count] == 4) {
    496 
    497                 self.totalContentLength = [[bytesobjectAtIndex:3]longLongValue];
    498 
    499             }
    500 
    501         }
    502 
    503     }else if (response.statusCode ==416){
    504 
    505         
    506 
    507         NSString *contentRange = [response.allHeaderFieldsvalueForKey:@"Content-Range"];
    508 
    509         if ([contentRange hasPrefix:@"bytes"]) {
    510 
    511             NSArray *bytes = [contentRangecomponentsSeparatedByCharactersInSet:[NSCharacterSetcharacterSetWithCharactersInString:@" -/"]];
    512 
    513             if ([bytes count] == 3) {
    514 
    515                 
    516 
    517                 self.totalContentLength = [[bytesobjectAtIndex:2]longLongValue];
    518 
    519                 if (self.totalReceivedContentLength==self.totalContentLength) {
    520 
    521                     
    522 
    523                     //说明已下完
    524 
    525                     
    526 
    527                     //更新进度
    528 
    529                     self.progressBlock();
    530 
    531                 }else{
    532 
    533                     
    534 
    535                     //416 Requested Range Not Satisfiable
    536 
    537                     self.error = [[NSErroralloc]initWithDomain:[self.urlabsoluteString]code:416userInfo:response.allHeaderFields];
    538 
    539                 }
    540 
    541             }
    542 
    543         }
    544 
    545         return;
    546 
    547     }else{
    548 
    549         
    550 
    551         //其他情况还没发现
    552 
    553         return;
    554 
    555     }
    556 
    557     
    558 
    559     //向文件追加数据
    560 
    561     NSFileHandle *fileHandle = [NSFileHandlefileHandleForUpdatingAtPath:self.targetPath];
    562 
    563     [fileHandle seekToEndOfFile]; //将节点跳到文件的末尾
    564 
    565     
    566 
    567     [fileHandle writeData:data];//追加写入数据
    568 
    569     [fileHandle closeFile];
    570 
    571     
    572 
    573     //更新进度
    574 
    575     self.totalReceivedContentLength += data.length;
    576 
    577     self.progressBlock();
    578 
    579 }
    580 
    581  
    582 
    583  
    584 
    585 @end
     

    经验证,如果app后台能运行,datatask是支持后台传输的。
    让您的app成为后台运行app非常简单:


    #import "AppDelegate.h"
    static UIBackgroundTaskIdentifier bgTask;


    @interface AppDelegate ()


    @end


    @implementation AppDelegate


    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        return YES;
    }


    - (void)applicationDidEnterBackground:(UIApplication *)application {
        
        [self getBackgroundTask];
    }


    - (void)applicationWillEnterForeground:(UIApplication *)application {
        
        [self endBackgroundTask];
    }


    /**
     *  获取后台任务
     */
    -(void)getBackgroundTask{
        
        NSLog(@"getBackgroundTask");
        UIBackgroundTaskIdentifier tempTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
            
        }];
        
        if (bgTask != UIBackgroundTaskInvalid) {
            
            [self endBackgroundTask];
        }
        
        bgTask = tempTask;
        
        [self performSelector:@selector(getBackgroundTask) withObject:nil afterDelay:120];
    }


    /**
     *  结束后台任务
     */
    -(void)endBackgroundTask{
        
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }


    @end

  • 相关阅读:
    [原创]java WEB学习笔记97:Spring学习---Spring 中的 Bean 配置:IOC 和 DI
    [原创]java WEB学习笔记96:Spring学习---Spring简介及HelloWord
    [原创]java WEB学习笔记95:Hibernate 目录
    [原创]java WEB学习笔记94:Hibernate学习之路---session 的管理,Session 对象的生命周期与本地线程绑定
    [原创]java WEB学习笔记93:Hibernate学习之路---Hibernate 缓存介绍,缓存级别,使用二级缓存的情况,二级缓存的架构集合缓存,二级缓存的并发策略,实现步骤,集合缓存,查询缓存,时间戳缓存
    [原创]java WEB学习笔记92:Hibernate学习之路-- -QBC 检索和本地 SQL 检索:基本的QBC 查询,带 AND 和 OR 的QBC,统计查询,排序,分页
    [原创]java WEB学习笔记91:Hibernate学习之路-- -HQL 迫切左外连接,左外连接,迫切内连接,内连接,关联级别运行时的检索策略 比较。理论,在于理解
    [原创]java WEB学习笔记90:Hibernate学习之路-- -HQL检索方式,分页查询,命名查询语句,投影查询,报表查询
    [原创]java WEB学习笔记89:Hibernate学习之路-- -Hibernate检索方式(5种),HQL介绍,实现功能,实现步骤,
    [原创]java WEB学习笔记88:Hibernate学习之路-- -Hibernate检索策略(立即检索,延迟检索,迫切左外连接检索)
  • 原文地址:https://www.cnblogs.com/allencelee/p/5810819.html
Copyright © 2011-2022 走看看