苹果提供的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
原标题:ios断点续传:NSURLSession和NSURLSessionDataTask实现
关键词:IOS