RQDownloadManager.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. //
  2. // RQDownloadManager.m
  3. // TEST
  4. //
  5. // Created by 张嵘 on 2018/10/22.
  6. // Copyright © 2018 张嵘. All rights reserved.
  7. //
  8. #import "RQDownloadManager.h"
  9. #import "AppDelegate.h"
  10. @interface RQDownloadManager ()<NSURLSessionDataDelegate>{
  11. NSMutableArray *_downloadModels;
  12. NSMutableArray *_completeModels;
  13. NSMutableArray *_downloadingModels;
  14. NSMutableArray *_pauseModels;
  15. BOOL _enableProgressLog;
  16. }
  17. @property (nonatomic, strong) NSOperationQueue *queue;
  18. @property (nonatomic, strong) NSURLSession *backgroundSession;
  19. @end
  20. static UIBackgroundTaskIdentifier bgTask;
  21. @implementation RQDownloadManager
  22. #pragma mark - 单例相关
  23. static id instace = nil;
  24. + (id)allocWithZone:(struct _NSZone *)zone {
  25. if (instace == nil) {
  26. static dispatch_once_t onceToken;
  27. dispatch_once(&onceToken, ^{
  28. instace = [super allocWithZone:zone];
  29. // 添加未捕获异常的监听
  30. [instace handleUncaughtExreption];
  31. // 添加监听
  32. [instace addObservers];
  33. // 创建缓存目录
  34. [instace createCacheDirectory];
  35. });
  36. }
  37. return instace;
  38. }
  39. - (instancetype)init {
  40. return instace;
  41. }
  42. + (instancetype)sharedManager {
  43. return [[self alloc] init];
  44. }
  45. - (id)copyWithZone:(struct _NSZone *)zone {
  46. return instace;
  47. }
  48. - (id)mutableCopyWithZone:(struct _NSZone *)zone {
  49. return instace;
  50. }
  51. #pragma mark - 单例初始化调用
  52. /**
  53. * 添加监听
  54. */
  55. - (void)addObservers {
  56. [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(recoverDownloadModels) name:UIApplicationDidFinishLaunchingNotification object:nil];
  57. [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(applicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil];
  58. [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(endBackgroundTask) name:UIApplicationWillEnterForegroundNotification object:nil];
  59. [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(getBackgroundTask) name:UIApplicationDidEnterBackgroundNotification object:nil];
  60. [[NSNotificationCenter defaultCenter] addObserver:instace selector:@selector(applicationWillTerminate) name:kNotificationUncaughtException object:nil];
  61. }
  62. /**
  63. * 创建缓存目录
  64. */
  65. - (void)createCacheDirectory {
  66. NSFileManager *fileManager = [NSFileManager defaultManager];
  67. if (![fileManager fileExistsAtPath:RQCachesDirectory]) {
  68. [fileManager createDirectoryAtPath:RQCachesDirectory withIntermediateDirectories:YES attributes:nil error:NULL];
  69. }
  70. NSLog(@"创建缓存目录:%@",RQCachesDirectory);
  71. }
  72. /**
  73. * 添加未捕获异常的监听
  74. */
  75. - (void)handleUncaughtExreption {
  76. [RQUncaughtExceptionHandler setDefaultHandler];
  77. }
  78. /**
  79. * 禁止打印进度日志
  80. */
  81. - (void)enableProgressLog:(BOOL)enable {
  82. _enableProgressLog = enable;
  83. }
  84. #pragma mark - 模型相关
  85. - (void)addDownloadModel:(RQDownloadModel *)model {
  86. if (![self checkExistWithDownloadModel:model]) {
  87. [self.downloadModels addObject:model];
  88. NSLog(@"下载模型添加成功");
  89. }
  90. }
  91. - (void)addDownloadModels:(NSArray<RQDownloadModel *> *)models {
  92. if ([models isKindOfClass:[NSArray class]]) {
  93. [models enumerateObjectsUsingBlock:^(RQDownloadModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  94. [self addDownloadModel:obj];
  95. }];
  96. }
  97. }
  98. -(BOOL)checkExistWithDownloadModel:(RQDownloadModel *)model {
  99. for (RQDownloadModel *tmpModel in self.downloadModels) {
  100. if ([tmpModel.urlString isEqualToString:model.urlString]) {
  101. NSLog(@"Tip:下载数据模型已存在");
  102. return YES;
  103. }
  104. }
  105. return NO;
  106. }
  107. - (RQDownloadModel *)downloadModelWithUrl:(NSString *)url {
  108. for (RQDownloadModel *tmpModel in self.downloadModels) {
  109. if ([url isEqualToString:tmpModel.urlString]) {
  110. return tmpModel;
  111. }
  112. }
  113. return nil;
  114. }
  115. #pragma mark - 单任务下载控制
  116. - (void)startWithDownloadModel:(RQDownloadModel *)model {
  117. if (model.status == RQDownloadStatus_Completed) {
  118. return;
  119. }
  120. [self addDownloadModel:model];
  121. //检查队列是否挂起
  122. if(self.queue.suspended){
  123. self.queue.suspended = NO;
  124. }
  125. model.operation = [[RQDownloadOperation alloc] initWithDownloadModel:model andSession:self.backgroundSession];
  126. [self.queue addOperation:model.operation];
  127. }
  128. //暂停后操作将销毁 若想继续执行 则需重新创建operation并添加
  129. - (void)suspendWithDownloadModel:(RQDownloadModel *)model {
  130. [self suspendWithDownloadModel:model forAll:NO];
  131. }
  132. - (void)suspendWithDownloadModel:(RQDownloadModel *)model forAll:(BOOL)forAll {
  133. if (forAll) {//暂停全部
  134. if (model.status == RQDownloadStatus_Running) {//下载中 则暂停
  135. [model.operation suspend];
  136. }else if (model.status == RQDownloadStatus_Waiting){//等待中 则取消
  137. [model.operation cancel];
  138. }
  139. }else{
  140. if (model.status == RQDownloadStatus_Running) {
  141. [model.operation suspend];
  142. }
  143. }
  144. model.operation = nil;
  145. }
  146. - (void)resumeWithDownloadModel:(RQDownloadModel *)model {
  147. if (model.status == RQDownloadStatus_Completed ||
  148. model.status == RQDownloadStatus_Running) {
  149. return;
  150. }
  151. //等待中 且操作已在队列中 则无需恢复
  152. if (model.status == RQDownloadStatus_Waiting && model.operation) {
  153. return;
  154. }
  155. model.operation = nil;
  156. //检查队列是否挂起
  157. if(self.queue.suspended){
  158. self.queue.suspended = NO;
  159. }
  160. model.operation = [[RQDownloadOperation alloc] initWithDownloadModel:model andSession:self.backgroundSession];
  161. [self.queue addOperation:model.operation];
  162. }
  163. - (void)stopWithDownloadModel:(RQDownloadModel *)model {
  164. [self stopWithDownloadModel:model forAll:NO];
  165. }
  166. - (void)stopWithDownloadModel:(RQDownloadModel *)model forAll:(BOOL)forAll {
  167. if (model.status != RQDownloadStatus_Completed) {
  168. [model.operation cancel];
  169. }
  170. //移除对应的下载文件
  171. if([RQFileManager fileExistsAtPath:model.destinationPath]){
  172. NSError *error = nil;
  173. [RQFileManager removeItemAtPath:model.destinationPath error:&error];
  174. if (error) {
  175. NSLog(@"Tip:下载文件移除失败,%@",error);
  176. }else{
  177. NSLog(@"Tip:下载文件移除成功");
  178. }
  179. }
  180. //释放operation
  181. model.operation = nil;
  182. //单个删除 则直接从数组中移除下载模型 否则等清空文件后统一移除
  183. if(!forAll){
  184. [self.downloadModels removeObject:model];
  185. }
  186. }
  187. #pragma mark - 批量下载相关
  188. /**
  189. * 批量下载操作
  190. */
  191. - (void)startWithDownloadModels:(NSArray<RQDownloadModel *> *)downloadModels {
  192. NSLog(@">>>%@前 operationCount = %zd", NSStringFromSelector(_cmd),self.queue.operationCount);
  193. [self.queue setSuspended:NO];
  194. [self addDownloadModels:downloadModels];
  195. [self operateTasksWithOperationType:RQOperationType_startAll];
  196. NSLog(@"<<<%@后 operationCount = %zd",NSStringFromSelector(_cmd),self.queue.operationCount);
  197. }
  198. /**
  199. * 暂停所有下载任务
  200. */
  201. - (void)suspendAll {
  202. [self.queue setSuspended:YES];
  203. [self operateTasksWithOperationType:RQOperationType_suspendAll];
  204. }
  205. /**
  206. * 恢复下载任务(进行中、已完成、等待中除外)
  207. */
  208. - (void)resumeAll {
  209. [self.queue setSuspended:NO];
  210. [self operateTasksWithOperationType:RQOperationType_resumeAll];
  211. }
  212. /**
  213. * 停止并删除下载任务
  214. */
  215. - (void)stopAll {
  216. //销毁前暂停队列 防止等待中的任务执行
  217. [self.queue setSuspended:YES];
  218. [self.queue cancelAllOperations];
  219. [self operateTasksWithOperationType:RQOperationType_stopAll];
  220. [self.queue setSuspended:NO];
  221. [self.downloadModels removeAllObjects];
  222. [self removeAllFiles];
  223. }
  224. - (void)operateTasksWithOperationType:(RQOperationType)operationType {
  225. [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  226. RQDownloadModel *downloadModel = obj;
  227. switch (operationType) {
  228. case RQOperationType_startAll:
  229. [self startWithDownloadModel:downloadModel];
  230. break;
  231. case RQOperationType_suspendAll:
  232. [self suspendWithDownloadModel:downloadModel forAll:YES];
  233. break;
  234. case RQOperationType_resumeAll:
  235. [self resumeWithDownloadModel:downloadModel];
  236. break;
  237. case RQOperationType_stopAll:
  238. [self stopWithDownloadModel:downloadModel forAll:YES];
  239. break;
  240. default:
  241. break;
  242. }
  243. }];
  244. }
  245. /**
  246. * 从备份恢复下载数据
  247. */
  248. - (void)recoverDownloadModels {
  249. if ([RQFileManager fileExistsAtPath:RQSavedDownloadModelsBackup]) {
  250. NSError * error = nil;
  251. [RQFileManager removeItemAtPath:RQSavedDownloadModelsFilePath error:nil];
  252. BOOL recoverSuccess = [RQFileManager copyItemAtPath:RQSavedDownloadModelsBackup toPath:RQSavedDownloadModelsFilePath error:&error];
  253. if (recoverSuccess) {
  254. NSLog(@"Tip:数据恢复成功");
  255. [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  256. RQDownloadModel *model = (RQDownloadModel *)obj;
  257. if (model.status == RQDownloadStatus_Running ||
  258. model.status == RQDownloadStatus_Waiting){
  259. [self startWithDownloadModel:model];
  260. }
  261. }];
  262. }else{
  263. NSLog(@"Tip:数据恢复失败,%@",error);
  264. }
  265. }
  266. }
  267. #pragma mark - 文件相关
  268. /**
  269. * 保存下载模型
  270. */
  271. - (void)saveData {
  272. [RQFileManager removeItemAtPath:RQSavedDownloadModelsFilePath error:nil];
  273. BOOL flag = [NSKeyedArchiver archiveRootObject:self.downloadModels toFile:RQSavedDownloadModelsFilePath];
  274. NSLog(@"Tip:下载数据保存路径%@",RQSavedDownloadModelsFilePath);
  275. NSLog(@"Tip:下载数据保存-%@",flag?@"成功!":@"失败");
  276. if (flag) {
  277. [self backupFile];
  278. }
  279. }
  280. /**
  281. * 备份下载模型
  282. */
  283. - (void)backupFile {
  284. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  285. NSError *error = nil;
  286. [self removeBackupFile];
  287. BOOL exist = [RQFileManager fileExistsAtPath:RQSavedDownloadModelsFilePath];
  288. if (exist) {
  289. BOOL backupSuccess = [RQFileManager copyItemAtPath:RQSavedDownloadModelsFilePath toPath:RQSavedDownloadModelsBackup error:&error];
  290. if (backupSuccess) {
  291. NSLog(@"Tip:数据备份成功");
  292. }else{
  293. NSLog(@"Tip:数据备份失败,%@",error);
  294. [self backupFile];
  295. }
  296. }
  297. });
  298. }
  299. /**
  300. * 移除备份
  301. */
  302. - (void)removeBackupFile {
  303. if ([RQFileManager fileExistsAtPath:RQSavedDownloadModelsBackup]) {
  304. NSError * error = nil;
  305. BOOL success = [RQFileManager removeItemAtPath:RQSavedDownloadModelsBackup error:&error];
  306. if (success) {
  307. NSLog(@"Tip:备份移除成功");
  308. }else{
  309. NSLog(@"Tip:备份移除失败,%@",error);
  310. }
  311. }
  312. }
  313. /**
  314. * 移除目录中所有文件
  315. */
  316. - (void)removeAllFiles {
  317. //返回路径中的文件数组
  318. NSArray * files = [[NSFileManager defaultManager] subpathsAtPath:RQCachesDirectory];
  319. for(NSString *p in files){
  320. NSError*error;
  321. NSString*path = [RQCachesDirectory stringByAppendingString:[NSString stringWithFormat:@"/%@",p]];
  322. if([[NSFileManager defaultManager] fileExistsAtPath:path]){
  323. BOOL isRemove = [[NSFileManager defaultManager]removeItemAtPath:path error:&error];
  324. if(isRemove) {
  325. NSLog(@"文件:%@-->清除成功",p);
  326. }else{
  327. NSLog(@"文件:%@-->清除失败",p);
  328. }
  329. }
  330. }
  331. }
  332. #pragma mark - Private Method
  333. #pragma mark - Getters/Setters
  334. - (NSMutableArray *)downloadModels {
  335. if (!_downloadModels) {
  336. //查看本地是否有数据
  337. NSFileManager *fileManager = [NSFileManager defaultManager];
  338. BOOL exist = [fileManager fileExistsAtPath:RQSavedDownloadModelsFilePath isDirectory:nil];
  339. if (exist) {//有 则读取本地数据
  340. _downloadModels = [NSKeyedUnarchiver unarchiveObjectWithFile:RQSavedDownloadModelsFilePath];
  341. }else{
  342. _downloadModels = [NSMutableArray array];
  343. }
  344. }
  345. return _downloadModels;
  346. }
  347. - (NSMutableArray *)completeModels {
  348. __block NSMutableArray *tmpArr = [NSMutableArray array];
  349. if (self.downloadModels) {
  350. [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  351. RQDownloadModel *model = obj;
  352. if (model.status == RQDownloadStatus_Completed) {
  353. [tmpArr addObject:model];
  354. }
  355. }];
  356. }
  357. _completeModels = tmpArr;
  358. return _completeModels;
  359. }
  360. - (NSMutableArray *)downloadingModels {
  361. __block NSMutableArray *tmpArr = [NSMutableArray array];
  362. if (self.downloadModels) {
  363. [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  364. RQDownloadModel *model = obj;
  365. if (model.status == RQDownloadStatus_Running) {
  366. [tmpArr addObject:model];
  367. }
  368. }];
  369. }
  370. _downloadingModels = tmpArr;
  371. return _downloadingModels;
  372. }
  373. - (NSMutableArray *)waitModels {
  374. __block NSMutableArray *tmpArr = [NSMutableArray array];
  375. if (self.downloadModels) {
  376. [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  377. RQDownloadModel *model = obj;
  378. if (model.status == RQDownloadStatus_Waiting) {
  379. [tmpArr addObject:model];
  380. }
  381. }];
  382. }
  383. return tmpArr;
  384. }
  385. - (NSMutableArray *)pauseModels {
  386. __block NSMutableArray *tmpArr = [NSMutableArray array];
  387. if (self.downloadModels) {
  388. [self.downloadModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  389. RQDownloadModel *model = obj;
  390. if (model.status == RQDownloadStatus_Suspended) {
  391. [tmpArr addObject:model];
  392. }
  393. }];
  394. }
  395. _pauseModels = tmpArr;
  396. return _pauseModels;
  397. }
  398. - (NSOperationQueue *)queue {
  399. if (!_queue) {
  400. _queue = [[NSOperationQueue alloc] init];
  401. [_queue setMaxConcurrentOperationCount:RQDownloadMaxConcurrentOperationCount];
  402. }
  403. return _queue;
  404. }
  405. - (void)setMaxConcurrentOperationCount:(NSInteger)maxConcurrentOperationCount {
  406. _maxConcurrentOperationCount = maxConcurrentOperationCount;
  407. self.queue.maxConcurrentOperationCount = _maxConcurrentOperationCount;
  408. }
  409. - (NSURLSession *)backgroundSession {
  410. if (!_backgroundSession) {
  411. NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[[NSBundle mainBundle] bundleIdentifier]];
  412. //不能传self.queue
  413. _backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
  414. }
  415. return _backgroundSession;
  416. }
  417. - (BOOL)enableProgressLog {
  418. return _enableProgressLog;
  419. }
  420. #pragma mark - 后台任务相关
  421. /**
  422. * 获取后台任务
  423. */
  424. - (void)getBackgroundTask {
  425. NSLog(@"getBackgroundTask");
  426. UIBackgroundTaskIdentifier tempTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
  427. }];
  428. if (bgTask != UIBackgroundTaskInvalid) {
  429. [self endBackgroundTask];
  430. }
  431. bgTask = tempTask;
  432. [self performSelector:@selector(getBackgroundTask) withObject:nil afterDelay:120];
  433. }
  434. /**
  435. * 结束后台任务
  436. */
  437. - (void)endBackgroundTask {
  438. [[UIApplication sharedApplication] endBackgroundTask:bgTask];
  439. bgTask = UIBackgroundTaskInvalid;
  440. }
  441. #pragma mark - Event Response
  442. /**
  443. * 应用强关或闪退时 保存下载数据
  444. */
  445. - (void)applicationWillTerminate {
  446. [self saveData];
  447. }
  448. #pragma mark - NSURLSessionDataDelegate
  449. /**
  450. * 接收到响应
  451. */
  452. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
  453. RQDownloadModel *downloadModel = dataTask.downloadModel;
  454. // 打开流
  455. [downloadModel.stream open];
  456. // 获得服务器这次请求 返回数据的总长度
  457. NSInteger totalLength = [response.allHeaderFields[@"Content-Length"] integerValue] + downloadModel.fileDownloadSize;
  458. downloadModel.fileTotalSize = totalLength;
  459. // 接收这个请求,允许接收服务器的数据
  460. completionHandler(NSURLSessionResponseAllow);
  461. }
  462. /**
  463. * 接收到服务器返回的数据
  464. */
  465. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
  466. NSLog(@"还在执行!");
  467. if (!dataTask.downloadModel) {
  468. return;
  469. }
  470. RQDownloadModel *downloadModel = dataTask.downloadModel;
  471. // 写入数据
  472. [downloadModel.stream write:data.bytes maxLength:data.length];
  473. // 下载进度
  474. NSInteger totalBytesWritten = downloadModel.fileDownloadSize;
  475. NSInteger totalBytesExpectedToWrite = downloadModel.fileTotalSize;
  476. double byts = totalBytesWritten * 1.0 / 1024 /1024;
  477. double total = totalBytesExpectedToWrite * 1.0 / 1024 /1024;
  478. NSString *text = [NSString stringWithFormat:@"%.1lfMB/%.1lfMB",byts,total];
  479. CGFloat progress = 1.0 * byts / total;
  480. downloadModel.statusText = text;
  481. downloadModel.progress = progress;
  482. }
  483. /**
  484. * 请求完毕 下载成功 | 失败
  485. */
  486. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
  487. RQDownloadModel *downloadModel = task.downloadModel;
  488. [downloadModel.stream close];
  489. downloadModel.stream = nil;
  490. task = nil;
  491. if (downloadModel.status == RQDownloadStatus_Completed) {
  492. [self saveData];
  493. }
  494. }
  495. @end