您的位置:首页 > 移动开发 > IOS开发

ios断点续传:NSURLSession和NSURLSessionDataTask实现

2015-10-21 14:34 507 查看
苹果提供的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 (^)(longlong totalReceivedContentLength,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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息