你的位置:首页 > 操作系统

[操作系统]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 MQLResumeManager102 103 104 105 /**106 107 * 设置成功、失败回调block108 109 *110 111 * @param success 成功回调block112 113 * @param failure 失败回调block114 115 */116 117 - (void)setCompletionBlockWithSuccess:(void (^)())success118 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 * 设置进度回调block164 165 *166 167 * @param progress168 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 safe214 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*)url260 261                targetPath:(NSString*)targetPath262 263                 success:(void (^)())success264 265                 failure:(void (^)(NSError*error))failure266 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 -- NSURLSessionDelegate378 379 /* The last message a session delegate receives. A session will only become380 381 * invalid because of a systemic error or when it has been382 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 -- NSURLSessionTaskDelegate398 399 /* Sent as the last message related to a specific task. Error may be400 401 * nil, which implies that no error occurred and this task is complete.402 403 */404 405 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask*)task406 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 -- NSURLSessionDataDelegate456 457 /* Sent when data is available for the delegate to consume. It is458 459 * assumed that the delegate will retain and not copy the data. As460 461 * the data may be discontiguous, you should use462 463 * [NSData enumerateByteRangesUsingBlock:] to access it.464 465 */466 467 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask468 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 Satisfiable536 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