ios断点续传:NSURLSession和NSURLSessionDataTask实现

47 篇文章 0 订阅
34 篇文章 0 订阅

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

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

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

1、配置NSURLRequest对象的Range请求头字段信息

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

3、创建NSURLSessionDataTask对象,启动任务。

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

直接上代码吧(MQLResumeDataTask类封装了断点续传,具体差看头问价及实现文件)

//
//  MQLResumeDataTask.h
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MQLResumeDataTask : NSObject

/// 断点续传请求
- (void)resumeDataTaskWithURL:(NSString *)URLString
                                    parameters:(id)parameters
                               savePath:(NSString*)savePath
                             downloadProgress:(void(^)(NSProgress *downloadProgress)) downloadProgressBlock
                                  completionHandler:(void(^)(NSURLResponse *response,  NSError *error))completionHandler;

/// 取消断点续传请求
- (void)cancelResumeDataTask;

@end

NS_ASSUME_NONNULL_END


//
//  MQLResumeDataTask.m
//

#import "MQLResumeDataTask.h"

@interface MQLResumeDataTask ()<NSURLSessionTaskDelegate, NSURLSessionDataDelegate>

@property (nonatomic, strong) NSFileManager *fileManager;   //用其获取文件大小
@property (nonatomic, strong) NSString *savePath;           //文件存放位置

@property (nonatomic, strong) NSURLSession *session;        //请求会话
@property (nonatomic, strong) NSOutputStream *outStream;    //输出流

@property (nonatomic, strong) NSURLSessionDataTask *dataTask;   //下载任务
@property (nonatomic, strong) NSProgress *downloadProgress;     //记录下载进度
@property (nonatomic, copy) void (^downloadProgressBlock)(NSProgress*); //下载进度回调
@property (nonatomic, copy) void (^completionHandler)(NSURLResponse*, NSError*);//下载完成回调

@end

@implementation MQLResumeDataTask

- (instancetype)init
{
    self = [super init];
    if (self) {
        _fileManager = [NSFileManager defaultManager];
        
        NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
        _session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
        
        _downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
        [_downloadProgress addObserver:self
                   forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                      options:NSKeyValueObservingOptionNew
                      context:NULL];
    }
    return self;
}

#pragma mark - NSProgress Tracking
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
   if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
}

-(void)dealloc{
    [self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
}

/// 断点续传请求
- (void)resumeDataTaskWithURL:(NSString *)URLString
                                     parameters:(id)parameters
                                       savePath:(NSString*)savePath
                               downloadProgress:(void(^)(NSProgress *downloadProgress)) downloadProgressBlock
     completionHandler:(void(^)(NSURLResponse *response, NSError * error))completionHandler {
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:URLString]];
    // 设置请求头for断点续传
    NSString *range = [NSString stringWithFormat:@"bytes=%lld-", [self caculateFileSizeWithPath:savePath]];
    [request setValue:range forHTTPHeaderField:@"Range"];

    //记录保存路径、下载进度回调、完成回调
    self.savePath = savePath;
    self.downloadProgressBlock = downloadProgressBlock;
    self.completionHandler = completionHandler;
    
    //创建任务并启动
    self.dataTask = [self.session dataTaskWithRequest:request];
    [self.dataTask resume];
}

/// 取消断点续传请求
- (void)cancelResumeDataTask{
    if (self.dataTask) {
        [self.dataTask cancel];
        self.dataTask = nil;
    }
}

//获取文件大小
- (int64_t)caculateFileSizeWithPath:(NSString *)filePath {
    if (![_fileManager fileExistsAtPath:filePath]) return 0;
    return [[_fileManager attributesOfItemAtPath:filePath error:nil] fileSize];
}

#pragma mark -- NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    //error.code == -999 || error.code == -1011)  //cancel  || timed out
    self.completionHandler(task.response, error);
    if (self.outStream) {
        [self.outStream close];
        self.outStream = nil;
    }
}

#pragma mark -- NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    //计算起始进度
    self.downloadProgress.totalUnitCount = response.expectedContentLength + [self caculateFileSizeWithPath:self.savePath];
    self.downloadProgress.completedUnitCount = [self caculateFileSizeWithPath:self.savePath];
    
    //创建输出流并打开
    _outStream = [[NSOutputStream alloc] initToFileAtPath:_savePath append:YES];
    [_outStream open];
    
    //允许继续请求
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
    completionHandler(disposition);
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    //追加数据
    [_outStream write:data.bytes maxLength:data.length];
    //变更已下载数据大小
    self.downloadProgress.completedUnitCount = [self caculateFileSizeWithPath:self.savePath];
}


@end

下面是使用上的演示:

#import "MQLResumeDataTask.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (nonatomic, strong) MQLResumeDataTask *task;

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString *file = [caches stringByAppendingPathComponent:@"test.mp4"];
    NSLog(@"%@", file);
    
    _task = [MQLResumeDataTask new];
    __weak typeof(self) weakSelf = self;
    [_task resumeDataTaskWithURL:@"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" parameters:@{} savePath:file downloadProgress:^(NSProgress * _Nonnull downloadProgress) {
        
        NSLog(@"%.2f", downloadProgress.fractionCompleted);
        dispatch_async(dispatch_get_main_queue(), ^{
            weakSelf.progressView.progress = downloadProgress.fractionCompleted;
        });
        
        
    } completionHandler:^(NSURLResponse * _Nonnull response, NSError * _Nullable error) {
        
        NSLog(@"%@", error);
    }];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [_task cancelResumeDataTask];
}

 

经验证,如果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

 

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值