转:ios断点续传:NSURLSession和NSURLSessionDataTask实现

  苹果提供的NSURLSessionDownloadTask虽然能实现断点续传,但是有些情况是无法处理的,比如程序强制退出或没有调用

cancelByProducingResumeData取消方法,这时就无法断点续传了。

使用NSURLSession和NSURLSessionDataTask实现断点续传的过程是:

1、配置NSMutableURLRequest对象的Range请求头字段信息

2、创建使用代理的NSURLSession对象

3、使用NSURLSession对象和NSMutableURLRequest对象创建NSURLSessionDataTask对象,启动任务。

4、在NSURLSessionDataDelegate的didReceiveData方法中追加获取下载数据到目标文件。

下面是具体实现,封装了一个续传管理器。可以直接拷贝到你的工程里,也可以参考我提供的DEMO:

//  MQLResumeManager.h

//

//  Created by MQL on 15/10/21.

//  Copyright © 2015年. All rights reserved.

//

#import

@interfaceMQLResumeManager :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,longlongtotalContentLength))progress;

/**

*启动断点续传下载请求

*/

-(void)start;

/**

*取消断点续传下载请求

*/

-(void)cancel;

@end

//

//  MQLResumeManager.m

//

//  Created byMQLon 15/10/21.

//  Copyright © 2015年. All rights reserved.

//

#import"MQLResumeManager.h"

typedefvoid(^completionBlock)();

typedefvoid(^progressBlock)();

@interfaceMQLResumeManager()

@property(nonatomic,strong)NSURLSession*session;//注意一个session只能有一个请求任务

@property(nonatomic,readwrite,retain)NSError*error;//请求出错

@property(nonatomic,readwrite,copy)completionBlockcompletionBlock;

@property(nonatomic,readwrite,copy)progressBlockprogressBlock;

@property(nonatomic,strong)NSURL*url;//文件资源地址

@property(nonatomic,strong)NSString*targetPath;//文件存放路径

@propertylonglongtotalContentLength;//文件总大小

@propertylonglongtotalReceivedContentLength;//已下载大小

/**

*设置成功、失败回调block

*

*  @param success成功回调block

*  @param failure失败回调block

*/

- (void)setCompletionBlockWithSuccess:(void(^)())success

failure:(void(^)(NSError*error))failure;

/**

*设置进度回调block

*

*  @param progress

*/

-(void)setProgressBlockWithProgress:(void(^)(longlongtotalReceivedContentLength,longlongtotalContentLength))progress;

/**

*获取文件大小

*  @param path文件路径

*  @return文件大小

*

*/

- (longlong)fileSizeForPath:(NSString*)path;

@end

@implementationMQLResumeManager

/**

*设置成功、失败回调block

*

*  @param success成功回调block

*  @param failure失败回调block

*/

- (void)setCompletionBlockWithSuccess:(void(^)())success

failure:(void(^)(NSError*error))failure{

__weaktypeof(self) weakSelf =self;

self.completionBlock= ^ {

dispatch_async(dispatch_get_main_queue(), ^{

if(weakSelf.error) {

if(failure) {

failure(weakSelf.error);

}

}else{

if(success) {

success();

}

}

});

};

}

/**

*设置进度回调block

*

*  @param progress

*/

-(void)setProgressBlockWithProgress:(void(^)(longlongtotalReceivedContentLength,longlongtotalContentLength))progress{

__weaktypeof(self) weakSelf =self;

self.progressBlock= ^{

dispatch_async(dispatch_get_main_queue(), ^{

progress(weakSelf.totalReceivedContentLength, weakSelf.totalContentLength);

});

};

}

/**

*获取文件大小

*  @param path文件路径

*  @return文件大小

*

*/

- (longlong)fileSizeForPath:(NSString*)path {

longlongfileSize =0;

NSFileManager*fileManager = [NSFileManagernew];//

not thread safe

if([fileManagerfileExistsAtPath:path]) {

NSError*error =nil;

NSDictionary*fileDict = [fileManagerattributesOfItemAtPath:patherror:&error];

if(!error && fileDict) {

fileSize = [fileDictfileSize];

}

}

returnfileSize;

}

/**

*创建断点续传管理对象,启动下载请求

*

*  @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,longlongtotalContentLength))progress{

MQLResumeManager*manager = [[MQLResumeManageralloc]init];

manager.url= url;

manager.targetPath= targetPath;

[managersetCompletionBlockWithSuccess:successfailure:failure];

[managersetProgressBlockWithProgress:progress];

manager.totalContentLength=0;

manager.totalReceivedContentLength=0;

returnmanager;

}

/**

*启动断点续传下载请求

*/

-(void)start{

NSMutableURLRequest*request = [[NSMutableURLRequestalloc]initWithURL:self.url];

longlongdownloadedBytes =self.totalReceivedContentLength= [selffileSizeForPath:self.targetPath];

if(downloadedBytes >0) {

NSString*requestRange = [NSStringstringWithFormat:@"bytes=%llu-", downloadedBytes];

[requestsetValue:requestRangeforHTTPHeaderField:@"Range"];

}else{

intfileDescriptor =open([self.targetPathUTF8String],O_CREAT|O_EXCL|O_RDWR,0666);

if(fileDescriptor >0) {

close(fileDescriptor);

}

}

NSURLSessionConfiguration*sessionConfiguration = [NSURLSessionConfigurationdefaultSessionConfiguration];

NSOperationQueue*queue = [[NSOperationQueuealloc]init];

self.session= [NSURLSessionsessionWithConfiguration:sessionConfigurationdelegate:selfdelegateQueue:queue];

NSURLSessionDataTask*dataTask = [self.sessiondataTaskWithRequest:request];

[dataTaskresume];

}

/**

*取消断点续传下载请求

*/

-(void)cancel{

if(self.session) {

[self.sessioninvalidateAndCancel];

self.session=nil;

}

}

#pragma mark -- NSURLSessionDelegate

/* The last message a session delegate receives.  A session will only become

* invalid because of a systemic error or when it has been

* explicitly invalidated, in which case the error parameter will be nil.

*/

- (void)URLSession:(NSURLSession*)session didBecomeInvalidWithError:(nullableNSError*)error{

NSLog(@"didBecomeInvalidWithError");

}

#pragma mark -- NSURLSessionTaskDelegate

/* Sent as the last message related to a specific task.  Error may be

* nil, which implies that no error occurred and this task is complete.

*/

- (void)URLSession:(NSURLSession*)session task:(NSURLSessionTask*)task

didCompleteWithError:(nullableNSError*)error{

NSLog(@"didCompleteWithError");

if(error ==nil&&self.error==nil) {

self.completionBlock();

}elseif(error !=nil){

if(error.code!= -999) {

self.error= error;

self.completionBlock();

}

}elseif(self.error!=nil){

self.completionBlock();

}

}

#pragma mark -- NSURLSessionDataDelegate

/* Sent when data is available for the delegate to consume.  It is

* assumed that the delegate will retain and not copy the data.  As

* the data may be discontiguous, you should use

* [NSData enumerateByteRangesUsingBlock:] to access it.

*/

- (void)URLSession:(NSURLSession*)session dataTask:(NSURLSessionDataTask*)dataTask

didReceiveData:(NSData*)data{

//根据status code的不同,做相应的处理

NSHTTPURLResponse*response = (NSHTTPURLResponse*)dataTask.response;

if(response.statusCode==200) {

self.totalContentLength= dataTask.countOfBytesExpectedToReceive;

}elseif(response.statusCode==206){

NSString*contentRange = [response.allHeaderFieldsvalueForKey:@"Content-Range"];

if([contentRangehasPrefix:@"bytes"]) {

NSArray*bytes = [contentRangecomponentsSeparatedByCharactersInSet:[NSCharacterSetcharacterSetWithCharactersInString:@"

-/"]];

if([bytescount] ==4) {

self.totalContentLength= [[bytesobjectAtIndex:3]longLongValue];

}

}

}elseif(response.statusCode==416){

NSString*contentRange = [response.allHeaderFieldsvalueForKey:@"Content-Range"];

if([contentRangehasPrefix:@"bytes"]) {

NSArray*bytes = [contentRangecomponentsSeparatedByCharactersInSet:[NSCharacterSetcharacterSetWithCharactersInString:@"

-/"]];

if([bytescount] ==3) {

self.totalContentLength= [[bytesobjectAtIndex:2]longLongValue];

if(self.totalReceivedContentLength==self.totalContentLength)

{

//说明已下完

//更新进度

self.progressBlock();

}else{

//416 Requested Range Not Satisfiable

self.error= [[NSErroralloc]initWithDomain:[self.urlabsoluteString]code:416userInfo:response.allHeaderFields];

}

}

}

return;

}else{

//其他情况还没发现

return;

}

//向文件追加数据

NSFileHandle*fileHandle = [NSFileHandlefileHandleForUpdatingAtPath:self.targetPath];

[fileHandleseekToEndOfFile];//将节点跳到文件的末尾

[fileHandlewriteData:data];//追加写入数据

[fileHandlecloseFile];

//更新进度

self.totalReceivedContentLength+= data.length;

self.progressBlock();

}

@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实现 - qianlima210210的专栏 - 博客频道 - CSDN.NET 

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容