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