ios 后台下载,app退出再进入可以断点续传 NSURLSessionDownloadTask(一)

想了解,多文件下载和管理,看这一篇文章,是在这基础上再次封装的:点击打开链接

使用NSURLSessionDataTask,进行封装下载的,看这篇文章 点击打开链接

使用:

#import "ViewController.h"
#import "BackgroundDownloadTool.h"

@interface ViewController ()<BackgroundDownloadToolDelegate> {
    BackgroundDownloadTool *tool;
}
@property (weak, nonatomic) IBOutlet UILabel *label;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    tool = [BackgroundDownloadTool new];
    tool.delegate = self;
    [tool initTask];
}

-(void)BackgroundDownloadToolCallbackProgress:(double)progress Error:(NSError *)error {
    NSLog(@"%f",progress);
    self.label.text = [NSString stringWithFormat:@"%f",progress];
}

- (IBAction)start:(id)sender {
    [tool startOrContinueDownload];
}

- (IBAction)stop:(id)sender {
    [tool pauseDownload];
}


主要代码:

AppDelegate.m

#import "AppDelegate.h"
#import "BackgroundDownloadTool.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    return YES;
}

-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
    [BackgroundDownloadTool sharedInstance].backgroundSessionCompletionHandler = completionHandler;
}

BackgroundDownloadTool.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@protocol BackgroundDownloadToolDelegate<NSObject>
@optional
-(void)BackgroundDownloadToolCallbackProgress:(double)progress Error:(NSError *)error identifier:(NSString *)identifier;
-(void)BackgroundDownloadToolDownloadingFinish:(NSString *)path identifier:(NSString *)identifier;
@end

@interface BackgroundDownloadTool : NSObject
@property(nonatomic,strong)NSString *identifier;//唯一SessionConfiguration
@property(nonatomic,strong)NSString *fileName;//保存的文件名
@property(nonatomic,strong)NSString *urlStr;

@property (nonatomic, weak) IBOutlet id<BackgroundDownloadToolDelegate> delegate;
@property (strong, nonatomic)NSData *resumeData;//已下载的数据
@property (nonatomic, copy) void (^backgroundSessionCompletionHandler)();

- (void)startOrContinueDownload;//开始或继续下载
- (void)pauseDownload;//暂停下载
-(void)cancelDownload;//取消下载

-(void)removeDownloadFile;//移除已下载好的文件
-(void)removeCacheDownloadFile;//移除缓存的文件

- (void)downloadProgress:(void (^)(CGFloat progress, NSError *err))downloadProgressBlock complement:(void (^)(NSString *path))completeBlock;

@end


BackgroundDownloadTool.m

#import "BackgroundDownloadTool.h"
#import "NSURLSession+TYCorrectedResumeData.h"

#define IS_IOS10ORLATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10)
#define IS_IOS8ORLATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8)

@interface BackgroundDownloadTool ()<NSURLSessionDelegate, NSURLSessionTaskDelegate,NSURLSessionDownloadDelegate> {
    NSString *tmpPath;
    BOOL _isDownloadStateCompleted;
    int64_t allSize;
    BOOL _isStart;
}

@property (nonatomic) NSURLSession *session;
@property (nonatomic) NSURLSessionDownloadTask *downloadTask;
@property (nonatomic,strong) NSString *DownloadPath;
@property (nonatomic,strong) NSString *CachePath;
@property (nonatomic,strong) NSString *Location;
@property (nonatomic,strong) NSFileManager *manage;
@property (strong, nonatomic) NSOperationQueue *queue;

@property(nonatomic,copy)void (^downloadProgressBlock)(CGFloat progress, NSError *err);
@property(nonatomic,copy)void (^completeBlock)(NSString *path);

@end

@implementation BackgroundDownloadTool
-(NSString *)Location {
    if (!_Location) {
        _Location =[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"BackgroundDownload"];
        [self.manage createDirectoryAtPath:_Location withIntermediateDirectories:YES attributes:nil error:nil];
    }
    return _Location;
}

- (NSString *)DownloadPath {
    if (!_DownloadPath) {
        _DownloadPath =[self.Location stringByAppendingPathComponent:self.fileName];
    }
    return _DownloadPath;
}

- (NSString *)CachePath {
    if (!_CachePath) {
        _CachePath =[self.Location stringByAppendingPathComponent:[NSString stringWithFormat:@"cashe%@",self.fileName]];
    }
    return _CachePath;
}

//初始化session
-(NSURLSession *)session {
    if (!_session) {
        if (_identifier) {
            if (IS_IOS8ORLATER) {
                NSURLSessionConfiguration *configure = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:_identifier];
                _session = [NSURLSession sessionWithConfiguration:configure delegate:self delegateQueue:self.queue];
            }else{
                _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration backgroundSessionConfiguration:_identifier] delegate:self delegateQueue:self.queue];
            }
        }else {
            _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:self.queue];
        }
    }
    return _session;
}

- (NSOperationQueue *)queue {
    if (!_queue) {
        _queue = [[NSOperationQueue alloc]init];
        _queue.maxConcurrentOperationCount = 1;
    }
    return _queue;
}

- (NSFileManager *)manage{
    if (!_manage){
        _manage = [NSFileManager defaultManager];
    }
    return _manage;
}

-(instancetype)init{
    if (self = [super init]) {
        self.identifier = @"BackgroundSession.tmp";
        self.urlStr = @"http://baobab.wdjcdn.com/1456317490140jiyiyuetai_x264.mp4";
        self.fileName = @"test.mp4";
    }
    return self;
}

+(BackgroundDownloadTool *)sharedInstance {
    static dispatch_once_t pred = 0;
    __strong static id internet = nil;
    dispatch_once(&pred, ^{
        internet = [[self alloc] init];
    });
    return internet;
}

- (void)downloadProgress:(void (^)(CGFloat progress, NSError *err))downloadProgressBlock complement:(void (^)(NSString *path))completeBlock {
    self.downloadProgressBlock = downloadProgressBlock;
    self.completeBlock = completeBlock;
}

-(void)initTask {
    NSURL *downloadURL = [NSURL URLWithString:self.urlStr];
    NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
    self.downloadTask = [self.session downloadTaskWithRequest:request];
}

-(NSInteger)AlreadyDownloadLength {
    return [[[NSFileManager defaultManager]attributesOfItemAtPath:self.DownloadPath error:nil][NSFileSize] integerValue];
}

// 是否已经下载
- (BOOL)isDownloadCompletedWithDownload {
    return [self.manage fileExistsAtPath:self.DownloadPath];
}

-(void)startOrContinueDownload {
    if (_isStart) {
        return;
    }
    if (![self isDownloadCompletedWithDownload]) {
        [self initTask];
        self.resumeData = [NSData dataWithContentsOfFile:self.CachePath];
        if (self.resumeData) {
            if (IS_IOS10ORLATER) {
                self.downloadTask = [self.session downloadTaskWithCorrectResumeData:self.resumeData];
            } else {
                self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
            }
        }
        _isStart = YES;
        [self.downloadTask resume];
    }else {
        NSLog(@"已经下载完成");
        !self.completeBlock?:self.completeBlock(self.DownloadPath);

        if ([self.delegate respondsToSelector:@selector(BackgroundDownloadToolDownloadingFinish:identifier:)]) {
            [self.delegate BackgroundDownloadToolDownloadingFinish:self.DownloadPath identifier:self.identifier];
        }
    }
}

-(void)cancelDownload {
    _isStart = NO;
    [self.downloadTask suspend];
    [self.downloadTask cancel];
    self.downloadTask = nil;
    [self removeCacheDownloadFile];
}

- (void)pauseDownload {
    _isStart = NO;
    if (![self isDownloadCompletedWithDownload]) {
        __weak __typeof(self) wSelf = self;
        [self.downloadTask cancelByProducingResumeData:^(NSData * resumeData) {
            __strong __typeof(wSelf) sSelf = wSelf;
            sSelf.resumeData = resumeData;
            [sSelf saveData:resumeData];
        }];
    }else {
        NSLog(@"已经下载完成");
    }
}

-(void)saveData:(NSData *)data {
    [data writeToFile:self.CachePath atomically:YES];
}

//监听进度
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    
    if (downloadTask == self.downloadTask) {
        allSize = totalBytesExpectedToWrite;
        double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
        dispatch_async(dispatch_get_main_queue(), ^{
            !self.downloadProgressBlock?:self.downloadProgressBlock(progress,nil);
            
            if ([self.delegate respondsToSelector:@selector(BackgroundDownloadToolCallbackProgress:Error:identifier:)]) {
            [self.delegate BackgroundDownloadToolCallbackProgress:progress Error:nil identifier:self.identifier];
            }
        });
    }
}

-(void)removeDownloadFile {
    NSError *error;
    if ([self.manage fileExistsAtPath:self.DownloadPath ] ) {
        [self.manage removeItemAtPath:self.DownloadPath  error:&error];
        if (error) {
            NSLog(@"removeItem error %@",error);
        }
    }
}

-(void)removeCacheDownloadFile {
    NSError *error;
    if ([self.manage fileExistsAtPath:self.CachePath ] ) {
        [self.manage removeItemAtPath:self.CachePath  error:&error];
        if (error) {
            NSLog(@"removeItem error %@",error);
        }
    }
}

//下载成功
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"downloadTask:%lu didFinishDownloadingToURL:%@", (unsigned long)downloadTask.taskIdentifier, location);
    NSString *locationString = [location path];
    NSError *error;
    [self removeDownloadFile];
    [self removeCacheDownloadFile];
    
    [self.manage moveItemAtPath:locationString toPath:self.DownloadPath error:&error];
    if (error) {
        NSLog(@"moveItemAtPath error %@",error);
    }
    _isStart = NO;
    !self.completeBlock?:self.completeBlock(self.DownloadPath);

    if ([self.delegate respondsToSelector:@selector(BackgroundDownloadToolDownloadingFinish:identifier:)]) {
        [self.delegate BackgroundDownloadToolDownloadingFinish:self.DownloadPath identifier:self.identifier];
    }
}

//下载完成
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    if (error) {
        _isStart = NO;
        if ([error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]) {
            NSData *data = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
            self.resumeData = data;
            [self saveData:data];
        }
    }
    
    double progress = (double)task.countOfBytesReceived / (double)task.countOfBytesExpectedToReceive;
    dispatch_async(dispatch_get_main_queue(), ^{
        !self.downloadProgressBlock?:self.downloadProgressBlock(progress,error);
        if ([self.delegate respondsToSelector:@selector(BackgroundDownloadToolCallbackProgress:Error:identifier:)]) {
            [self.delegate BackgroundDownloadToolCallbackProgress:progress Error:error identifier:self.identifier];
        }
    });
    self.downloadTask = nil;
}

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    if (self.backgroundSessionCompletionHandler) {
        self.backgroundSessionCompletionHandler();
    }
}


@end


NSURLSession+TYCorrectedResumeData.h

#import "NSURLSession+TYCorrectedResumeData.h"
#import <UIKit/UIKit.h>

#define IS_IOS10ORLATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10)

@implementation NSURLSession (TYCorrectedResumeData)

- (NSURLSessionDownloadTask *)downloadTaskWithCorrectResumeData:(NSData *)resumeData {
    NSString *kResumeCurrentRequest = @"NSURLSessionResumeCurrentRequest";
    NSString *kResumeOriginalRequest = @"NSURLSessionResumeOriginalRequest";
    
    NSData *cData = correctResumeData(resumeData);
    cData = cData?cData:resumeData;
    NSURLSessionDownloadTask *task = [self downloadTaskWithResumeData:cData];
    NSMutableDictionary *resumeDic = getResumeDictionary(cData);
    if (resumeDic) {
        if (task.originalRequest == nil) {
            NSData *originalReqData = resumeDic[kResumeOriginalRequest];
            NSURLRequest *originalRequest = [NSKeyedUnarchiver unarchiveObjectWithData:originalReqData ];
            if (originalRequest) {
                [task setValue:originalRequest forKey:@"originalRequest"];
            }
        }
        if (task.currentRequest == nil) {
            NSData *currentReqData = resumeDic[kResumeCurrentRequest];
            NSURLRequest *currentRequest = [NSKeyedUnarchiver unarchiveObjectWithData:currentReqData];
            if (currentRequest) {
                [task setValue:currentRequest forKey:@"currentRequest"];
            }
        }
        
    }
    return task;
}

#pragma mark- private

NSData * correctRequestData(NSData *data) {
    if (!data) {
        return nil;
    }
    // return the same data if it's correct
    if ([NSKeyedUnarchiver unarchiveObjectWithData:data] != nil) {
        return data;
    }
    NSMutableDictionary *archive = [[NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil] mutableCopy];
    
    if (!archive) {
        return nil;
    }
    NSInteger k = 0;
    id objectss = archive[@"$objects"];
    while ([objectss[1] objectForKey:[NSString stringWithFormat:@"$%ld",k]] != nil) {
        k += 1;
    }
    NSInteger i = 0;
    while ([archive[@"$objects"][1] objectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",i]] != nil) {
        NSMutableArray *arr = archive[@"$objects"];
        NSMutableDictionary *dic = arr[1];
        id obj = [dic objectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",i]];
        if (obj) {
            [dic setValue:obj forKey:[NSString stringWithFormat:@"$%ld",i+k]];
            [dic removeObjectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",i]];
            [arr replaceObjectAtIndex:1 withObject:dic];
            archive[@"$objects"] = arr;
        }
        i++;
    }
    if ([archive[@"$objects"][1] objectForKey:@"__nsurlrequest_proto_props"] != nil) {
        NSMutableArray *arr = archive[@"$objects"];
        NSMutableDictionary *dic = arr[1];
        id obj = [dic objectForKey:@"__nsurlrequest_proto_props"];
        if (obj) {
            [dic setValue:obj forKey:[NSString stringWithFormat:@"$%ld",i+k]];
            [dic removeObjectForKey:@"__nsurlrequest_proto_props"];
            [arr replaceObjectAtIndex:1 withObject:dic];
            archive[@"$objects"] = arr;
        }
    }
    // Rectify weird "NSKeyedArchiveRootObjectKey" top key to NSKeyedArchiveRootObjectKey = "root"
    if ([archive[@"$top"] objectForKey:@"NSKeyedArchiveRootObjectKey"] != nil) {
        [archive[@"$top"] setObject:archive[@"$top"][@"NSKeyedArchiveRootObjectKey"] forKey: NSKeyedArchiveRootObjectKey];
        [archive[@"$top"] removeObjectForKey:@"NSKeyedArchiveRootObjectKey"];
    }
    // Reencode archived object
    NSData *result = [NSPropertyListSerialization dataWithPropertyList:archive format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil];
    return result;
}

NSMutableDictionary *getResumeDictionary(NSData *data) {
    NSMutableDictionary *iresumeDictionary = nil;
    if (IS_IOS10ORLATER) {
        id root = nil;
        id  keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
        @try {
            root = [keyedUnarchiver decodeTopLevelObjectForKey:@"NSKeyedArchiveRootObjectKey" error:nil];
            if (root == nil) {
                root = [keyedUnarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:nil];
            }
        } @catch(NSException *exception) {
            
        }
        [keyedUnarchiver finishDecoding];
        iresumeDictionary = [root mutableCopy];
    }
    
    if (iresumeDictionary == nil) {
        iresumeDictionary = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil];
    }
    return iresumeDictionary;
}

NSData *correctResumeData(NSData *data) {
    NSString *kResumeCurrentRequest = @"NSURLSessionResumeCurrentRequest";
    NSString *kResumeOriginalRequest = @"NSURLSessionResumeOriginalRequest";
    if (data == nil) {
        return  nil;
    }
    NSMutableDictionary *resumeDictionary = getResumeDictionary(data);
    if (resumeDictionary == nil) {
        return nil;
    }
    resumeDictionary[kResumeCurrentRequest] = correctRequestData(resumeDictionary[kResumeCurrentRequest]);
    resumeDictionary[kResumeOriginalRequest] = correctRequestData(resumeDictionary[kResumeOriginalRequest]);
    NSData *result = [NSPropertyListSerialization dataWithPropertyList:resumeDictionary format:NSPropertyListXMLFormat_v1_0 options:0 error:nil];
    return result;
}

@end



我的业余技术微信公众号:YKJGZH,欢迎大家进入


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值