简述
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的时间做处理
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通知过后,无法调用对应方法,推测应该是跟内存释放的时机有关系,由于时间有限,作者没有进行深入研究,有兴趣的同学可以自行探究一下,或者有了解的大佬可以留言告知一下