ios断点续传:NSURLSession和NSURLSessionDataTask实现

苹果提供的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)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,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 (^)(longlong totalReceivedContentLength,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 (^)(longlong totalReceivedContentLength,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;
}


@end

posted @ 2016-08-26 16:18  Allence  阅读(1856)  评论(0编辑  收藏  举报