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
ios断点续传:NSURLSession和NSURLSessionDataTask实现的更多相关文章
- iOS开发-NSURLSession详解
Core Foundation中NSURLConnection在2003年伴随着Safari浏览器的发行,诞生的时间比较久远,iOS升级比较快,AFNetWorking在3.0版本删除了所有基于NSU ...
- iOS - 网络 - NSURLSession
1.NSURLSession基础 NSURLConnection在开发中会使用的越来越少,iOS9已经将NSURLConnection废弃,现在最低版本一般适配iOS,所以也可以使用.NSURLCon ...
- iOS网络NSURLSession使用详解
一.整体介绍 NSURLSession在2013年随着iOS7的发布一起面世,苹果对它的定位是作为NSURLConnection的替代者,然后逐步将NSURLConnection退出历史舞台.现在使用 ...
- iOS基础 - NSURLSession
使用URLSession所有的网络访问都是有缓存的,缓存文件自动保存在tmp文件夹中,URLSession本身实现的时候,就是少量多次的! l 使用defaultSessionConfiguratio ...
- 数据存储之iOS断点续传
iOS里面实现断点续传 第三方框架之AFN 代码实现 一.iOS里面实现断点续传 1⃣️AFN基于NSURL 1.性能和稳定性略差.针对JSON.XML.Plist和Image四种数据结构封装了各自处 ...
- iOS使用NSURLSession发送POST请求,后台无法接受到请求过来的参数
iOS中发送POST请求,有时需要设置Content-Type,尤其是上传图片的时候. application/x-www-form-urlencoded: 窗体数据被编码为名称/值对.这是标准的编码 ...
- iOS imageio nsurlsession 渐进式图片下载
一.图片常用加载格式分两种 一般线性式 和交错/渐进式 自上而下线性式 先模糊再清晰 就概率上而言线性式使用最多,应为他所占空间普片比渐进式小.而这两种方式对于app端开发人员无需关心,这种图片存储格 ...
- iOS 应用开发中的断点续传实践总结
断点续传概述 断点续传就是从文件上次中断的地方开始重新下载或上传数据,而不是从文件开头.(本文的断点续传仅涉及下载,上传不在讨论之内)当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者 ...
- iOS开发之Alamofire源码解析前奏--NSURLSession全家桶
今天博客的主题不是Alamofire, 而是iOS网络编程中经常使用的NSURLSession.如果你想看权威的NSURLSession的东西,那么就得去苹果官方的开发中心去看了,虽然是英文的,但是结 ...
随机推荐
- C# .net页面乱码
可在web.config中设置 <globalization requestEncoding="utf-8" responseEncoding="utf-8&quo ...
- node.js异步编程解决方案之Promise用法
node.js异步编程解决方案之Promise var dbBase = require('../db/db_base'); var school_info_db = require('../db/s ...
- log4.j
导入第三方jar包 第一步:右击工程名,新建一个文件夹(Fold),名字为lib. 第二步:把第三方jar包复制到lib目录下. 第三步:右击工程名---->properties ----> ...
- Visual Studio Code 设置中文语言版本
设置方法有两种: 方法一1.选择扩展 搜索“Language”,在下列选项选择 Chinese (Simplified) Language Pack for Visual Studio Code安装, ...
- PHP基础--两个数组相加
在PHP中,当两个数组相加时,会把第二个数组的取值添加到第一个数组上,同时覆盖掉下标相同的值: <?php $a = array("a" => "apple& ...
- App后台开发架构实践笔记
1 App后台入门 1.1 App后台的功能 (1)远程存储数据: (2)消息中转. 1.2 App后台架构 架构设计的流程 (1) 根据App的设计,梳理出App的业务流程: (2) 把每个业务流程 ...
- liunx增强命令
查找命令 grep 格式:grep [option] pattern [file] 实例: ps -ef | grep sshd 查找指定 ssh 服务进程 ps -ef | grep sshd | ...
- c#MD5加密解密
MD5的全称是Message-Digest Algorithm 5(信息-摘要算法),在90年代初由MIT Laboratory for Computer Science和RSA Data Secur ...
- 从github下载一个单一文件
以ubuntu + wget为例 1) 浏览器中打开需要需要下载的文件 2) 点击 raw按钮 3) 从浏览器地址栏中拷贝地址 4) wget + 地址
- 我的HTML总结之HTML发展史
HTML是Web统一语言,这些容纳在尖括号里的简单标签,构成了如今的Web. 1991年,Tim Berners-Lee编写了一份叫做“HTML标签”的文档,里面包含了大约20个用来标记网页的HTML ...