ios斷點續傳:NSURLSession和NSURLSessionDataTask實現

蘋果提供的NSURLSessionDownloadTask雖然能實現斷點續傳,可是有些狀況是沒法處理的,好比程序強制退出或沒有調用session

cancelByProducingResumeData取消方法,這時就沒法斷點續傳了。app

 

使用NSURLSession和NSURLSessionDataTask實現斷點續傳的過程是:async

一、配置NSMutableURLRequest對象的Range請求頭字段信息ide

二、建立使用代理的NSURLSession對象this

三、使用NSURLSession對象和NSMutableURLRequest對象建立NSURLSessionDataTask對象,啓動任務。atom

四、在NSURLSessionDataDelegate的didReceiveData方法中追加獲取下載數據到目標文件。url

 

下面是具體實現,封裝了一個續傳管理器。能夠直接拷貝到你的工程裏,也能夠參考我提供的DEMO:http://pan.baidu.com/s/1c0BHToWspa

 

//

//  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;
}


@endcode

相關文章
相關標籤/搜索