123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613 |
- //
- // RQDownloadManager.m
- // TEST
- //
- // Created by 张嵘 on 2018/10/22.
- // Copyright © 2018 张嵘. All rights reserved.
- //
- #import "RQDownloadManager.h"
- #import "AppDelegate.h"
- @interface RQDownloadManager ()<NSURLSessionDataDelegate>{
- NSMutableArray *_downloadModels;
- NSMutableArray *_completeModels;
- NSMutableArray *_downloadingModels;
- NSMutableArray *_pauseModels;
- BOOL _enableProgressLog;
- }
- @property (nonatomic, strong) NSOperationQueue *queue;
- @property (nonatomic, strong) NSURLSession *backgroundSession;
- @end
- static UIBackgroundTaskIdentifier bgTask;
- @implementation RQDownloadManager
- #pragma mark - 单例相关
- static id instace = nil;
- + (id)allocWithZone:(struct _NSZone *)zone {
- if (instace == nil) {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- instace = [super allocWithZone:zone];
- // 添加未捕获异常的监听
- [instace handleUncaughtExreption];
- // 添加监听
- [instace addObservers];
- // 创建缓存目录
- [instace createCacheDirectory];
- });
- }
- return instace;
- }
- - (instancetype)init {
- return instace;
- }
- + (instancetype)sharedManager {
- return [[self alloc] init];
- }
- - (id)copyWithZone:(struct _NSZone *)zone {
- return instace;
- }
- - (id)mutableCopyWithZone:(struct _NSZone *)zone {
- return instace;
- }
- #pragma mark - 单例初始化调用
- /**
- * 添加监听
- */
- - (void)addObservers {
- [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(recoverDownloadModels) name:UIApplicationDidFinishLaunchingNotification object:nil];
-
- [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(applicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil];
-
- [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(endBackgroundTask) name:UIApplicationWillEnterForegroundNotification object:nil];
-
- [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(getBackgroundTask) name:UIApplicationDidEnterBackgroundNotification object:nil];
-
- [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(applicationWillTerminate) name:kNotificationUncaughtException object:nil];
- }
- /**
- * 创建缓存目录
- */
- - (void)createCacheDirectory {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- if (![fileManager fileExistsAtPath:RQCachesDirectory]) {
- [fileManager createDirectoryAtPath:RQCachesDirectory withIntermediateDirectories:YES attributes:nil error:NULL];
- }
- NSLog(@"创建缓存目录:%@",RQCachesDirectory);
- }
- /**
- * 添加未捕获异常的监听
- */
- - (void)handleUncaughtExreption {
- [RQUncaughtExceptionHandler setDefaultHandler];
- }
- /**
- * 禁止打印进度日志
- */
- - (void)enableProgressLog:(BOOL)enable {
- _enableProgressLog = enable;
- }
- #pragma mark - 模型相关
- - (void)addDownloadModel:(RQDownloadModel *)model {
- if (![self checkExistWithDownloadModel:model]) {
- [self.downloadModels addObject:model];
- NSLog(@"下载模型添加成功");
- }
- }
- - (void)addDownloadModels:(NSArray<RQDownloadModel *> *)models {
- if ([models isKindOfClass:[NSArray class]]) {
- [models enumerateObjectsUsingBlock:^(RQDownloadModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- [self addDownloadModel:obj];
- }];
- }
- }
- -(BOOL)checkExistWithDownloadModel:(RQDownloadModel *)model {
-
- for (RQDownloadModel *tmpModel in self.downloadModels) {
- if ([tmpModel.urlString isEqualToString:model.urlString]) {
- NSLog(@"Tip:下载数据模型已存在");
- return YES;
- }
- }
- return NO;
- }
- - (RQDownloadModel *)downloadModelWithUrl:(NSString *)url {
- for (RQDownloadModel *tmpModel in self.downloadModels) {
- if ([url isEqualToString:tmpModel.urlString]) {
- return tmpModel;
- }
- }
- return nil;
- }
- #pragma mark - 单任务下载控制
- - (void)startWithDownloadModel:(RQDownloadModel *)model {
- if (model.status == RQDownloadStatus_Completed) {
- return;
- }
-
- [self addDownloadModel:model];
-
- //检查队列是否挂起
- if(self.queue.suspended){
- self.queue.suspended = NO;
- }
-
- model.operation = [[RQDownloadOperation alloc] initWithDownloadModel:model andSession:self.backgroundSession];
- [self.queue addOperation:model.operation];
- }
- //暂停后操作将销毁 若想继续执行 则需重新创建operation并添加
- - (void)suspendWithDownloadModel:(RQDownloadModel *)model {
- [self suspendWithDownloadModel:model forAll:NO];
- }
- - (void)suspendWithDownloadModel:(RQDownloadModel *)model forAll:(BOOL)forAll {
- if (forAll) {//暂停全部
- if (model.status == RQDownloadStatus_Running) {//下载中 则暂停
- [model.operation suspend];
- }else if (model.status == RQDownloadStatus_Waiting){//等待中 则取消
- [model.operation cancel];
- }
- }else{
- if (model.status == RQDownloadStatus_Running) {
- [model.operation suspend];
- }
- }
-
- model.operation = nil;
- }
- - (void)resumeWithDownloadModel:(RQDownloadModel *)model {
- if (model.status == RQDownloadStatus_Completed ||
- model.status == RQDownloadStatus_Running) {
- return;
- }
- //等待中 且操作已在队列中 则无需恢复
- if (model.status == RQDownloadStatus_Waiting && model.operation) {
- return;
- }
- model.operation = nil;
-
- //检查队列是否挂起
- if(self.queue.suspended){
- self.queue.suspended = NO;
- }
-
- model.operation = [[RQDownloadOperation alloc] initWithDownloadModel:model andSession:self.backgroundSession];
- [self.queue addOperation:model.operation];
-
- }
- - (void)stopWithDownloadModel:(RQDownloadModel *)model {
- [self stopWithDownloadModel:model forAll:NO];
- }
- - (void)stopWithDownloadModel:(RQDownloadModel *)model forAll:(BOOL)forAll {
- if (model.status != RQDownloadStatus_Completed) {
- [model.operation cancel];
- }
-
- //移除对应的下载文件
- if([RQFileManager fileExistsAtPath:model.destinationPath]){
- NSError *error = nil;
- [RQFileManager removeItemAtPath:model.destinationPath error:&error];
- if (error) {
- NSLog(@"Tip:下载文件移除失败,%@",error);
- }else{
- NSLog(@"Tip:下载文件移除成功");
- }
- }
-
- //释放operation
- model.operation = nil;
-
- //单个删除 则直接从数组中移除下载模型 否则等清空文件后统一移除
- if(!forAll){
- [self.downloadModels removeObject:model];
- }
- }
- #pragma mark - 批量下载相关
- /**
- * 批量下载操作
- */
- - (void)startWithDownloadModels:(NSArray<RQDownloadModel *> *)downloadModels {
- NSLog(@">>>%@前 operationCount = %zd", NSStringFromSelector(_cmd),self.queue.operationCount);
- [self.queue setSuspended:NO];
- [self addDownloadModels:downloadModels];
- [self operateTasksWithOperationType:RQOperationType_startAll];
- NSLog(@"<<<%@后 operationCount = %zd",NSStringFromSelector(_cmd),self.queue.operationCount);
- }
- /**
- * 暂停所有下载任务
- */
- - (void)suspendAll {
- [self.queue setSuspended:YES];
- [self operateTasksWithOperationType:RQOperationType_suspendAll];
- }
- /**
- * 恢复下载任务(进行中、已完成、等待中除外)
- */
- - (void)resumeAll {
- [self.queue setSuspended:NO];
- [self operateTasksWithOperationType:RQOperationType_resumeAll];
- }
- /**
- * 停止并删除下载任务
- */
- - (void)stopAll {
- //销毁前暂停队列 防止等待中的任务执行
- [self.queue setSuspended:YES];
- [self.queue cancelAllOperations];
- [self operateTasksWithOperationType:RQOperationType_stopAll];
- [self.queue setSuspended:NO];
- [self.downloadModels removeAllObjects];
- [self removeAllFiles];
- }
- - (void)operateTasksWithOperationType:(RQOperationType)operationType {
- [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- RQDownloadModel *downloadModel = obj;
- switch (operationType) {
- case RQOperationType_startAll:
- [self startWithDownloadModel:downloadModel];
- break;
- case RQOperationType_suspendAll:
- [self suspendWithDownloadModel:downloadModel forAll:YES];
- break;
- case RQOperationType_resumeAll:
- [self resumeWithDownloadModel:downloadModel];
- break;
- case RQOperationType_stopAll:
- [self stopWithDownloadModel:downloadModel forAll:YES];
- break;
- default:
- break;
- }
- }];
- }
- /**
- * 从备份恢复下载数据
- */
- - (void)recoverDownloadModels {
- if ([RQFileManager fileExistsAtPath:RQSavedDownloadModelsBackup]) {
- NSError * error = nil;
- [RQFileManager removeItemAtPath:RQSavedDownloadModelsFilePath error:nil];
- BOOL recoverSuccess = [RQFileManager copyItemAtPath:RQSavedDownloadModelsBackup toPath:RQSavedDownloadModelsFilePath error:&error];
- if (recoverSuccess) {
- NSLog(@"Tip:数据恢复成功");
-
- [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- RQDownloadModel *model = (RQDownloadModel *)obj;
- if (model.status == RQDownloadStatus_Running ||
- model.status == RQDownloadStatus_Waiting){
- [self startWithDownloadModel:model];
- }
- }];
- }else{
- NSLog(@"Tip:数据恢复失败,%@",error);
- }
- }
- }
- #pragma mark - 文件相关
- /**
- * 保存下载模型
- */
- - (void)saveData {
- [RQFileManager removeItemAtPath:RQSavedDownloadModelsFilePath error:nil];
- BOOL flag = [NSKeyedArchiver archiveRootObject:self.downloadModels toFile:RQSavedDownloadModelsFilePath];
- NSLog(@"Tip:下载数据保存路径%@",RQSavedDownloadModelsFilePath);
- NSLog(@"Tip:下载数据保存-%@",flag?@"成功!":@"失败");
-
- if (flag) {
- [self backupFile];
- }
- }
- /**
- * 备份下载模型
- */
- - (void)backupFile {
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- NSError *error = nil;
- [self removeBackupFile];
- BOOL exist = [RQFileManager fileExistsAtPath:RQSavedDownloadModelsFilePath];
- if (exist) {
- BOOL backupSuccess = [RQFileManager copyItemAtPath:RQSavedDownloadModelsFilePath toPath:RQSavedDownloadModelsBackup error:&error];
- if (backupSuccess) {
- NSLog(@"Tip:数据备份成功");
- }else{
- NSLog(@"Tip:数据备份失败,%@",error);
- [self backupFile];
- }
- }
- });
- }
- /**
- * 移除备份
- */
- - (void)removeBackupFile {
- if ([RQFileManager fileExistsAtPath:RQSavedDownloadModelsBackup]) {
- NSError * error = nil;
- BOOL success = [RQFileManager removeItemAtPath:RQSavedDownloadModelsBackup error:&error];
- if (success) {
- NSLog(@"Tip:备份移除成功");
- }else{
- NSLog(@"Tip:备份移除失败,%@",error);
- }
- }
- }
- /**
- * 移除目录中所有文件
- */
- - (void)removeAllFiles {
- //返回路径中的文件数组
- NSArray * files = [[NSFileManager defaultManager] subpathsAtPath:RQCachesDirectory];
-
- for(NSString *p in files){
- NSError*error;
-
- NSString*path = [RQCachesDirectory stringByAppendingString:[NSString stringWithFormat:@"/%@",p]];
-
- if([[NSFileManager defaultManager] fileExistsAtPath:path]){
- BOOL isRemove = [[NSFileManager defaultManager]removeItemAtPath:path error:&error];
- if(isRemove) {
- NSLog(@"文件:%@-->清除成功",p);
- }else{
- NSLog(@"文件:%@-->清除失败",p);
- }
- }
- }
- }
- #pragma mark - Private Method
- #pragma mark - Getters/Setters
- - (NSMutableArray *)downloadModels {
- if (!_downloadModels) {
- //查看本地是否有数据
- NSFileManager *fileManager = [NSFileManager defaultManager];
- BOOL exist = [fileManager fileExistsAtPath:RQSavedDownloadModelsFilePath isDirectory:nil];
-
- if (exist) {//有 则读取本地数据
- _downloadModels = [NSKeyedUnarchiver unarchiveObjectWithFile:RQSavedDownloadModelsFilePath];
- }else{
- _downloadModels = [NSMutableArray array];
- }
- }
- return _downloadModels;
- }
- - (NSMutableArray *)completeModels {
- __block NSMutableArray *tmpArr = [NSMutableArray array];
- if (self.downloadModels) {
- [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- RQDownloadModel *model = obj;
- if (model.status == RQDownloadStatus_Completed) {
- [tmpArr addObject:model];
- }
- }];
- }
-
- _completeModels = tmpArr;
- return _completeModels;
- }
- - (NSMutableArray *)downloadingModels {
- __block NSMutableArray *tmpArr = [NSMutableArray array];
- if (self.downloadModels) {
- [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- RQDownloadModel *model = obj;
- if (model.status == RQDownloadStatus_Running) {
- [tmpArr addObject:model];
- }
- }];
- }
-
- _downloadingModels = tmpArr;
- return _downloadingModels;
- }
- - (NSMutableArray *)waitModels {
- __block NSMutableArray *tmpArr = [NSMutableArray array];
- if (self.downloadModels) {
- [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- RQDownloadModel *model = obj;
- if (model.status == RQDownloadStatus_Waiting) {
- [tmpArr addObject:model];
- }
- }];
- }
- return tmpArr;
- }
- - (NSMutableArray *)pauseModels {
- __block NSMutableArray *tmpArr = [NSMutableArray array];
- if (self.downloadModels) {
- [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- RQDownloadModel *model = obj;
- if (model.status == RQDownloadStatus_Suspended) {
- [tmpArr addObject:model];
- }
- }];
- }
- _pauseModels = tmpArr;
- return _pauseModels;
- }
- - (NSOperationQueue *)queue {
- if (!_queue) {
- _queue = [[NSOperationQueue alloc] init];
- [_queue setMaxConcurrentOperationCount:RQDownloadMaxConcurrentOperationCount];
- }
- return _queue;
- }
- - (void)setMaxConcurrentOperationCount:(NSInteger)maxConcurrentOperationCount {
- _maxConcurrentOperationCount = maxConcurrentOperationCount;
- self.queue.maxConcurrentOperationCount = _maxConcurrentOperationCount;
- }
- - (NSURLSession *)backgroundSession {
- if (!_backgroundSession) {
- NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[[NSBundle mainBundle] bundleIdentifier]];
- //不能传self.queue
- _backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
- }
-
- return _backgroundSession;
- }
- - (BOOL)enableProgressLog {
- return _enableProgressLog;
- }
- #pragma mark - 后台任务相关
- /**
- * 获取后台任务
- */
- - (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;
- }
- #pragma mark - Event Response
- /**
- * 应用强关或闪退时 保存下载数据
- */
- - (void)applicationWillTerminate {
- [self saveData];
- }
- #pragma mark - NSURLSessionDataDelegate
- /**
- * 接收到响应
- */
- - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
-
- RQDownloadModel *downloadModel = dataTask.downloadModel;
-
- // 打开流
- [downloadModel.stream open];
-
- // 获得服务器这次请求 返回数据的总长度
- NSInteger totalLength = [response.allHeaderFields[@"Content-Length"] integerValue] + downloadModel.fileDownloadSize;
- downloadModel.fileTotalSize = totalLength;
-
- // 接收这个请求,允许接收服务器的数据
- completionHandler(NSURLSessionResponseAllow);
- }
- /**
- * 接收到服务器返回的数据
- */
- - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
-
- NSLog(@"还在执行!");
- if (!dataTask.downloadModel) {
- return;
- }
-
- RQDownloadModel *downloadModel = dataTask.downloadModel;
-
- // 写入数据
- [downloadModel.stream write:data.bytes maxLength:data.length];
-
- // 下载进度
- NSInteger totalBytesWritten = downloadModel.fileDownloadSize;
- NSInteger totalBytesExpectedToWrite = downloadModel.fileTotalSize;
-
- double byts = totalBytesWritten * 1.0 / 1024 /1024;
- double total = totalBytesExpectedToWrite * 1.0 / 1024 /1024;
- NSString *text = [NSString stringWithFormat:@"%.1lfMB/%.1lfMB",byts,total];
-
- CGFloat progress = 1.0 * byts / total;
-
- downloadModel.statusText = text;
- downloadModel.progress = progress;
- }
- /**
- * 请求完毕 下载成功 | 失败
- */
- - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
- RQDownloadModel *downloadModel = task.downloadModel;
- [downloadModel.stream close];
- downloadModel.stream = nil;
- task = nil;
- if (downloadModel.status == RQDownloadStatus_Completed) {
- [self saveData];
- }
- }
- @end
|