苹果提供的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