IOS NSURLSessionDataTask实现断点续传


转自:http://blog.csdn.net/qianlima210210/article/details/49303703

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



//

//  MQLResumeManager.m

//

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

//  Copyright © 2015. All rights reserved.

//


#import "MQLResumeManager.h"


typedef void (^completionBlock)();

typedef void (^progressBlock)();


@interface MQLResumeManager ()<NSURLSessionDelegate,NSURLSessionTaskDelegate>


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

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

@property(nonatomic,readwrite,copy)completionBlockcompletionBlock;

@property(nonatomic,readwrite,copy)progressBlock progressBlock;


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

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

@property longlong totalContentLength;            //文件总大小

@property longlong totalReceivedContentLength;    //已下载大小


/**

 *  设置成功、失败回调block

 *

 *  @param success 成功回调block

 *  @param failure 失败回调block

 */

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

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


/**

 *  设置进度回调block

 *

 *  @param progress

 */

-(void)setProgressBlockWithProgress:(void (^)(longlongtotalReceivedContentLength,longlong totalContentLength))progress;


/**

 *  获取文件大小

 *  @param path 文件路径

 *  @return 文件大小

 *

 */

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


@end


@implementation MQLResumeManager


/**

 *  设置成功、失败回调block

 *

 *  @param success 成功回调block

 *  @param failure 失败回调block

 */

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

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

    

    __weak typeof(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,longlong totalContentLength))progress{

    

    __weak typeof(self) weakSelf =self;

    self.progressBlock = ^{

        

        dispatch_async(dispatch_get_main_queue(), ^{

            

            progress(weakSelf.totalReceivedContentLength, weakSelf.totalContentLength);

        });

    };

}


/**

 *  获取文件大小

 *  @param path 文件路径

 *  @return 文件大小

 *

 */

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

    

    long long fileSize =0;

    NSFileManager *fileManager = [NSFileManagernew];// not thread safe

    if ([fileManager fileExistsAtPath:path]) {

        NSError *error = nil;

        NSDictionary *fileDict = [fileManager attributesOfItemAtPath:path error:&error];

        if (!error && fileDict) {

            fileSize = [fileDict fileSize];

        }

    }

    return fileSize;

}


/**

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

 *

 *  @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{

    

    MQLResumeManager *manager = [[MQLResumeManageralloc]init];

    

    manager.url = url;

    manager.targetPath = targetPath;

    [manager setCompletionBlockWithSuccess:successfailure:failure];

    [manager setProgressBlockWithProgress:progress];

    

    manager.totalContentLength =0;

    manager.totalReceivedContentLength =0;

    

    return manager;

}


/**

 *  启动断点续传下载请求

 */

-(void)start{

    

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

    

    longlong downloadedBytes =self.totalReceivedContentLength = [selffileSizeForPath:self.targetPath];

    if (downloadedBytes > 0) {

        

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

        [request setValue:requestRange forHTTPHeaderField:@"Range"];

    }else{

        

        int fileDescriptor = 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];

    [dataTask resume];

}


/**

 *  取消断点续传下载请求

 */

-(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:(nullable NSError *)error{

    

    NSLog(@"didCompleteWithError");

    

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

        

        self.completionBlock();

        

    }else if (error !=nil){

        

        if (error.code != -999) {

            

            self.error = error;

            self.completionBlock();

        }

        

    }else if (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;

        

    }else if (response.statusCode ==206){

        

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

        if ([contentRange hasPrefix:@"bytes"]) {

            NSArray *bytes = [contentRangecomponentsSeparatedByCharactersInSet:[NSCharacterSetcharacterSetWithCharactersInString:@" -/"]];

            if ([bytes count] == 4) {

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

            }

        }

    }else if (response.statusCode ==416){

        

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

        if ([contentRange hasPrefix:@"bytes"]) {

            NSArray *bytes = [contentRangecomponentsSeparatedByCharactersInSet:[NSCharacterSetcharacterSetWithCharactersInString:@" -/"]];

            if ([bytes count] == 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];

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

    

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

    [fileHandle closeFile];

    

    //更新进度

    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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值