国内最全IT社区平台 联系我们 | 收藏本站
华晨云阿里云优惠2
您当前位置:首页 > php开源 > 综合技术 > 使用NSURLSession和NSURLSessionDataTask实现断点续传

使用NSURLSession和NSURLSessionDataTask实现断点续传

来源:程序员人生   发布时间:2016-03-24 09:11:50 阅读次数:2842次


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

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


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

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

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

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

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


下面是具体实现,封装了1个续传管理器。可以直接拷贝到你的工程里,也能够参考我提供的DEMO:http://pan.baidu.com/s/1c0BHToW


//

//  MQLResumeManager.h

//

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

//  Copyright ? 2015. All rights reserved.

//


#import


@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 (^)(longlong totalReceivedContentLength, long long 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;   //注意1个session只能有1个要求任务

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

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

@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 (^)(longlong totalReceivedContentLength, long long 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 (^)(longlong totalReceivedContentLength, long long 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 (^)(longlong totalReceivedContentLength, long long 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:sessionConfiguration delegate: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

生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
程序员人生
------分隔线----------------------------
分享到:
------分隔线----------------------------
关闭
程序员人生