转:ios断点续传:NSURLSession和NSURLSes

2016-10-01  本文已影响715人  西门欥雪

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

上一篇下一篇

猜你喜欢

热点阅读