iOS-NSURLSessionDownloadTask离线断点下载

257 阅读2分钟

简述

NSURLSessionDownloadTask提供了一个cancelByProducingResumeData的方法来支持断点下载,但是没有离线断点下载对应的API,本文便是针对这个问题的一种解决方案

实现

生成一个管理下载任务的单例来管理下载任务

在初始化单例的时候监听UIApplicationWillTerminateNotification通知,以便在APP被kill的时候作出对应的处理

CLDownloadManager.h

@interface CLDownloadManager : NSObject
@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, strong) NSURLSessionDownloadTask *task;
//用于session的delegate任务调用队列
@property (nonatomic, strong) NSOperationQueue *sessionDelegateQueue;
@property (nonatomic, strong) NSCondition * invalidateConditaion;

+ (instancetype)manager;
- (void)downloadWithUrl:(NSString *)url;
@end

创建一把NSCondition锁

NSCondition是对mutex和cond的封装,是一把互斥锁,支持线程睡眠,节约资源,这把锁用于在APP将要被杀死的时候阻塞主线程,让session的delegate队列有时间取消任务 根据文档可以知道app在接受到将要kill通知过后大约有5s的时间做处理

image.png

CLDownloadManager.m

+ (instancetype)manager{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [CLDownloadManager new];
        [_instance instanceInit];
    });
    return _instance;
}

- (void)instanceInit{
    self.invalidateConditaion = [[NSCondition alloc] init];
    [[NSNotificationCenter defaultCenter] addObserver:_instance selector:@selector(saveTempFile) name:UIApplicationWillTerminateNotification object:nil];
    self.sessionDelegateQueue = [[NSOperationQueue alloc] init];
    self.sessionDelegateQueue.maxConcurrentOperationCount = 1;
    self.sessionDelegateQueue.qualityOfService = NSQualityOfServiceUserInteractive;
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self currentDateStr]];
    config.allowsCellularAccess = YES;
    config.timeoutIntervalForRequest = 30;
    self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:self.sessionDelegateQueue];
}

- (void)downloadWithUrl:(NSString *)url{
    NSFileManager *fileManager = NSFileManager.defaultManager;
    if (![fileManager fileExistsAtPath:[self getTmpFileUrl]]) {
        NSURL *taskUrl = [NSURL URLWithString:url];
        _task = [self.session downloadTaskWithURL:taskUrl];
    }else{
        [self daskWithResumeData];
    }
    [_task resume];
}
//在下次打开下载页面的时候可以从沙盒中取出保存的data进行断点下载
- (void)daskWithResumeData{
    NSFileManager *fileManager = NSFileManager.defaultManager;
    NSData *downloadData = [fileManager contentsAtPath:[self getTmpFileUrl]];
    _task = [self.session downloadTaskWithResumeData:downloadData];
}

/**
核心代码,利用condition锁的机制为下载数据提供保存的机会,重新打开APP后可以利用
如果是多个任务可以利用dispatch_group来进行处理
*/
- (void)saveTempFile{
    [_task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        [resumeData writeToFile:[self getTmpFileUrl] atomically:YES];
        [self.invalidateConditaion lock];
        [self.invalidateConditaion broadcast];
        [self.invalidateConditaion unlock];
    }];
    [_invalidateConditaion lock];
    [_invalidateConditaion wait];
    [_invalidateConditaion unlock];
}

//未下载完的临时文件url地址
-(NSString*)getTmpFileUrl{
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *filePath = [docPath stringByAppendingPathComponent:@"download.archive"];
    return filePath;
}

//获取当前时间 下载id标识用
- (NSString *)currentDateStr{
    NSDate *currentDate = [NSDate date];//获取当前时间,日期
    NSTimeInterval timeInterval = [currentDate timeIntervalSince1970];
    return [NSString stringWithFormat:@"%.f",timeInterval];
}

/**
session的delegate代码在此省略
*/

为什么需要单例?

在APP被kill的时候,需要单例来进行文件的保存,经过测试,如果不是单例收到将要kill通知过后,无法调用对应方法,推测应该是跟内存释放的时机有关系,由于时间有限,作者没有进行深入研究,有兴趣的同学可以自行探究一下,或者有了解的大佬可以留言告知一下

参考代码

github.com/libobjc/SGD…