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

原文地址:https://www.cnblogs.com/allencelee/p/5810819.html