customDownloadManager.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. //
  2. // customDownloadManager.m
  3. // Private-X
  4. //
  5. // Created by xd h on 2024/7/1.
  6. //
  7. #import "customDownloadManager.h"
  8. #import "customDownloadOperation.h"
  9. #import "customDownloadCacheManager.h"
  10. @interface customDownloadManager ()<NSURLSessionDataDelegate>
  11. //排队等候下载的下载地址数组
  12. @property(nonatomic,strong) NSMutableArray *downloadWaitingUrlArr;
  13. //正在下载的下载地址数组
  14. @property(nonatomic,strong) NSMutableArray *downloadingOperationArr;
  15. @end
  16. @implementation customDownloadManager
  17. + (instancetype)shareManager {
  18. static customDownloadManager *_instance;
  19. static dispatch_once_t onceToken;
  20. dispatch_once(&onceToken, ^{
  21. _instance = [[self alloc] init];
  22. });
  23. return _instance;
  24. }
  25. - (instancetype)init {
  26. if (self = [super init]) {
  27. _maxDownLoadCount = 1;
  28. //[self registeNotification];
  29. }
  30. return self;
  31. }
  32. - (NSString*)uid{
  33. if(!_uid || _uid.length == 0){
  34. return @"customUserName";
  35. }
  36. return _uid;
  37. }
  38. /** 添加要下载的 网络连接 */
  39. - (void)addDownloadWithURLs:(NSArray *)urls{
  40. for (NSString *addUrl in urls) {
  41. BOOL needAddType = YES;
  42. //1. 排查下载中
  43. for (customDownloadOperation *operationDoing in self.downloadingOperationArr) {
  44. if([operationDoing.url isEqualToString:addUrl]){
  45. needAddType = NO;
  46. break;
  47. }
  48. }
  49. //1. 排查等待下载
  50. for (NSString *waitUrl in self.downloadWaitingUrlArr) {
  51. if([waitUrl isEqualToString:addUrl]){
  52. needAddType = NO;
  53. break;
  54. }
  55. }
  56. if(needAddType){
  57. [self.downloadWaitingUrlArr addObject:addUrl];
  58. }
  59. }
  60. //启动下载
  61. [self beginDownload];
  62. }
  63. //在添加下载地址后 启动下载
  64. - (void)beginDownload
  65. {
  66. @synchronized (self) {
  67. if(self.downloadingOperationArr.count == _maxDownLoadCount){
  68. HLog(@"正在下载的数量达到了最大值 %ld",_maxDownLoadCount)
  69. return;
  70. }
  71. if(self.downloadWaitingUrlArr.count == 0){
  72. HLog(@"没有等待中的下载任务")
  73. return;
  74. }
  75. NSInteger canAddTaskNumber = _maxDownLoadCount - self.downloadingOperationArr.count;
  76. for (int i=0; i<canAddTaskNumber; i++) {
  77. if(self.downloadWaitingUrlArr.count >= 1){
  78. //创建下载任务
  79. NSString *downloadUrl = self.downloadWaitingUrlArr.firstObject;
  80. NSURLSession *session = [self creatNewSessionFun];
  81. customDownloadOperation * operation = [[customDownloadOperation alloc] initWith:downloadUrl session:session];
  82. //等待下载中的任务
  83. [self.downloadWaitingUrlArr removeObjectAtIndex:0];
  84. //添加到下载中数组
  85. [self.downloadingOperationArr addObject:operation];
  86. [operation.dataTask resume];
  87. }
  88. }
  89. }
  90. }
  91. /** 开始任务(不会自动添加任务,列队中没有就直接返回) 后续改为会自动添加任务 */
  92. - (void)startDownLoadWithUrl:(NSString *)url
  93. {
  94. BOOL needAddType = YES;
  95. //1. 排查下载中
  96. for (customDownloadOperation *operationDoing in self.downloadingOperationArr) {
  97. if([operationDoing.url isEqualToString:url]){
  98. needAddType = NO;
  99. break;
  100. }
  101. }
  102. //1. 排查等待下载
  103. for (NSString *waitUrl in self.downloadWaitingUrlArr) {
  104. if([waitUrl isEqualToString:url]){
  105. needAddType = NO;
  106. break;
  107. }
  108. }
  109. if(needAddType){
  110. [self.downloadWaitingUrlArr addObject:url];
  111. }
  112. //启动下载
  113. [self beginDownload];
  114. }
  115. /** 删除任务(删除下载url内容的任务) */
  116. - (void)deleteDownloadWithUrl:(NSString *)url
  117. {
  118. //1.检测等待下载的任务
  119. for (NSString*waitingUrl in self.downloadWaitingUrlArr) {
  120. if ([waitingUrl isEqualToString:url]) {
  121. [self.downloadWaitingUrlArr removeObject:waitingUrl];
  122. break;
  123. }
  124. }
  125. //2.检测下载中的任务
  126. for (customDownloadOperation *operationDoing in self.downloadingOperationArr) {
  127. if([operationDoing.url isEqualToString:url]){
  128. [operationDoing.dataTask cancel];
  129. // operationDoing.dataTask = nil;
  130. // operationDoing.session = nil;
  131. //
  132. // [operationDoing.handle closeFile];
  133. // operationDoing.handle = nil;
  134. //
  135. // [self.downloadingOperationArr removeObject:operationDoing];
  136. break;
  137. }
  138. }
  139. //3. 删除本地文件
  140. [customDownloadCacheManager deleteFileWithUrl:url];
  141. //4.进行下一个任务
  142. //[self beginDownload];
  143. }
  144. /** 暂停任务(暂停下载url内容的任务) */
  145. - (void)supendDownloadWithUrl:(NSString *)url
  146. {
  147. //1.检测等待下载的任务
  148. for (NSString*waitingUrl in self.downloadWaitingUrlArr) {
  149. if ([waitingUrl isEqualToString:url]) {
  150. [self.downloadWaitingUrlArr removeObject:waitingUrl];
  151. break;
  152. }
  153. }
  154. //2.检测下载中的任务
  155. for (customDownloadOperation *operationDoing in self.downloadingOperationArr) {
  156. if([operationDoing.url isEqualToString:url]){
  157. operationDoing.isManualCancel = YES;
  158. if(operationDoing.dataTask){
  159. [operationDoing.dataTask cancel];
  160. }
  161. // operationDoing.dataTask = nil;
  162. // operationDoing.session = nil;
  163. //
  164. // [operationDoing.handle closeFile];
  165. // operationDoing.handle = nil;
  166. //
  167. // //删除后 didCompleteWithError 不再处理
  168. // [self.downloadingOperationArr removeObject:operationDoing];
  169. break;
  170. }
  171. }
  172. //进行下一个任务
  173. //[self beginDownload];
  174. }
  175. /** 暂停当前所有的下载任务 下载任务不会从列队中删除 */
  176. - (void)suspendAllDownloadTask
  177. {
  178. //1.删除等待下载的任务
  179. [self.downloadWaitingUrlArr removeAllObjects];
  180. //2.检测下载中的任务
  181. for (customDownloadOperation *operationDoing in self.downloadingOperationArr) {
  182. operationDoing.isManualCancel = YES;
  183. if(operationDoing.dataTask){
  184. [operationDoing.dataTask cancel];
  185. }
  186. // operationDoing.dataTask = nil;
  187. // operationDoing.session = nil;
  188. // [operationDoing.handle closeFile];
  189. // operationDoing.handle = nil;
  190. }
  191. [self.downloadingOperationArr removeAllObjects];
  192. }
  193. #pragma mark 重新启动因为网络失败而停止的任务
  194. - (void)reDownloadNetworkTaskBy:(NSString*)url
  195. {
  196. @synchronized (self) {
  197. [self.downloadWaitingUrlArr insertObject:url atIndex:0];
  198. }
  199. [self beginDownload];
  200. }
  201. #pragma mark - <NSURLSessionDataDelegate>
  202. // ssl 服务 证书信任
  203. - (void)URLSession:(NSURLSession *)session
  204. didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  205. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
  206. if(![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"]) {
  207. return;
  208. }
  209. // 信任该插件
  210. NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
  211. // 第一个参数 告诉系统如何处置
  212. completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
  213. }
  214. //当请求协议是https的时候回调用该方法
  215. //Challenge 挑战 质询(受保护空间)
  216. //NSURLAuthenticationMethodServerTrust 服务器信任证书
  217. - (void)URLSession:(NSURLSession *)session
  218. task:(NSURLSessionTask *)task
  219. didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  220. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
  221. if(![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"]) {
  222. return;
  223. }
  224. // 信任该插件
  225. NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
  226. // 第一个参数 告诉系统如何处置
  227. completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
  228. }
  229. // 接受到响应调用
  230. - (void)URLSession:(NSURLSession *)session
  231. dataTask:(NSURLSessionDataTask *)dataTask
  232. didReceiveResponse:(NSURLResponse *)response
  233. completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
  234. // 将响应交给列队处理
  235. BOOL canDownload = [self handleOperateby:dataTask WithResponse:response];
  236. if(canDownload){
  237. // 允许下载
  238. completionHandler(NSURLSessionResponseAllow);
  239. }
  240. else{
  241. // 不允许下载
  242. completionHandler(NSURLSessionResponseCancel);
  243. }
  244. }
  245. // 接受到数据碎片 的时候调用,调用多次
  246. - (void)URLSession:(NSURLSession *)session
  247. dataTask:(NSURLSessionDataTask *)dataTask
  248. didReceiveData:(NSData *)data {
  249. // 接收到session 下载碎片交个列队管理
  250. [self dataTask:dataTask didReceiveData:data];
  251. }
  252. // <NSURLSessionDataDelegate> 完成下载
  253. - (void)URLSession:(NSURLSession *)session
  254. task:(NSURLSessionTask *)task
  255. didCompleteWithError:(nullable NSError *)error {
  256. [self task:task didCompleteWithError:error];
  257. }
  258. - (void)URLSession:(NSURLSession *)session
  259. task:(NSURLSessionTask *)task
  260. needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler {
  261. }
  262. - (void)URLSession:(NSURLSession *)session
  263. task:(NSURLSessionTask *)task
  264. didSendBodyData:(int64_t)bytesSent
  265. totalBytesSent:(int64_t)totalBytesSent
  266. totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
  267. }
  268. #pragma mark 处理接收到的数据
  269. // 接收到相应时
  270. - (BOOL)handleOperateby:(NSURLSessionTask*)dataTask WithResponse:(NSURLResponse *)response {
  271. customDownloadOperation *operation = nil;
  272. for (customDownloadOperation *operationDoing in self.downloadingOperationArr) {
  273. if(operationDoing.dataTask == dataTask){
  274. operation = operationDoing;
  275. break;
  276. }
  277. }
  278. if (!operation) {
  279. HLog(@"没找到当前下载任务");
  280. operation.downloadState = customDownloadStateFailed;
  281. [dataTask cancel];
  282. return NO;
  283. }
  284. // 检查response是否是NSHTTPURLResponse的实例
  285. if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
  286. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
  287. NSInteger statusCode = httpResponse.statusCode;
  288. HLog(@"HTTP Status Code: %ld", (long)statusCode);
  289. if(statusCode == 404){
  290. operation.downloadState = customDownloadStateFailed;
  291. operation.isFile404Cancel = YES;
  292. return NO;
  293. }
  294. }
  295. // 总的size
  296. if (operation.currentSize + response.expectedContentLength == 0) {
  297. HLog(@"下载数据回调异常");
  298. operation.downloadState = customDownloadStateFailed;
  299. return NO;
  300. }
  301. operation.totalSize = operation.currentSize + response.expectedContentLength;
  302. // 创建空的文件夹
  303. if (operation.currentSize == 0) {
  304. // 创建空的文件
  305. [[NSFileManager defaultManager] createFileAtPath:operation.fullPath contents:nil attributes:nil];
  306. }
  307. // 创建文件句柄
  308. operation.handle = [NSFileHandle fileHandleForWritingAtPath:operation.fullPath];
  309. // 文件句柄移动到文件末尾 位置 // 返回值是 unsign long long
  310. [operation.handle seekToEndOfFile];
  311. // 开始下载记录文件下载信息
  312. operation.downloadState = customDownloadStateDoing;
  313. return YES;
  314. }
  315. - (void)dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
  316. {
  317. customDownloadOperation *operation = nil;
  318. for (customDownloadOperation *operationDoing in self.downloadingOperationArr) {
  319. if(operationDoing.dataTask == dataTask){
  320. operation = operationDoing;
  321. break;
  322. }
  323. }
  324. if (!operation) {
  325. HLog(@"没找到当前下载任务");
  326. [dataTask cancel];
  327. return;
  328. }
  329. //手动取消任务
  330. if (operation.isManualCancel) {
  331. return;
  332. }
  333. // 获得已经下载的文件大小
  334. operation.currentSize += data.length;
  335. HLog(@"currentSize:%lld---progress:%.2f", operation.currentSize, 1.00*operation.currentSize/operation.totalSize);
  336. // 写入文件
  337. if(operation.handle){
  338. [operation.handle writeData:data];
  339. }
  340. // 下载中通知
  341. [self operationDoningWithOperation:operation];
  342. }
  343. - (void)task:(NSURLSessionTask *)dataTask didCompleteWithError:(NSError *)error
  344. {
  345. customDownloadOperation *operation = nil;
  346. for (customDownloadOperation *operationDoing in self.downloadingOperationArr) {
  347. if(operationDoing.dataTask == dataTask){
  348. operation = operationDoing;
  349. [self.downloadingOperationArr removeObject:operationDoing];
  350. break;
  351. }
  352. }
  353. if (!operation) {
  354. HLog(@"没找到当前下载任务");
  355. //[dataTask suspend];
  356. return;
  357. }
  358. if(operation.dataTask){
  359. //[operation.dataTask cancel];
  360. operation.dataTask = nil;
  361. operation.session = nil;
  362. if(operation.handle){
  363. // 关闭文件句柄
  364. [operation.handle closeFile];
  365. // 释放文件句柄
  366. operation.handle = nil;
  367. }
  368. }
  369. if(error && (error.code == -1005 || error.code == -1009) && !operation.isFile404Cancel){//网络中断
  370. HLog(@"reDownloadNetworkTaskBy");
  371. //延时几秒再次启动这个任务
  372. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  373. [self reDownloadNetworkTaskBy:operation.url];
  374. });
  375. return;
  376. }
  377. if(operation.isManualCancel){
  378. [self beginDownload];
  379. return;
  380. }
  381. // 完成下载 通知 block
  382. if (error ||operation.downloadState == customDownloadStateFailed) {
  383. [self operationFailedWithOperation:operation];
  384. } else {
  385. operation.isFinished = YES;
  386. operation.timeStamp = [iTools getNowTimeStamp];
  387. [self operationSuccessWithOperation:operation];
  388. }
  389. [self beginDownload];
  390. }
  391. #pragma mark 发送通知
  392. // 失败某一个operation 保存本地 通知外界
  393. - (void)operationFailedWithOperation:(customDownloadOperation *)operation {
  394. HLog(@"DownloadTaskExeError \n %@",operation.url);
  395. operation.downloadState = customDownloadStateFailed;
  396. [[NSNotificationCenter defaultCenter] postNotificationName:customDownloadTaskExeError object:nil userInfo:@{@"operation" : operation}];
  397. }
  398. // 重启某一个operation 保存本地 通知外界
  399. - (void)operationDoningWithOperation:(customDownloadOperation *)operation {
  400. HLog(@"DownloadTaskExeing");
  401. NSTimeInterval curTime = [[NSDate date] timeIntervalSince1970];
  402. NSTimeInterval timeDiff = curTime - operation.preNotTimeInterval;
  403. HLog(@"控制刷新时间为1秒:%f",timeDiff);
  404. CGFloat RefreshTimer = 0.8;
  405. if(operation.totalSize <= 5*1024*1024){//小于5M
  406. RefreshTimer = 0.2;
  407. }
  408. if (operation.preNotTimeInterval <= 0
  409. || timeDiff > RefreshTimer ) {
  410. operation.preNotTimeInterval = curTime;
  411. [[NSNotificationCenter defaultCenter] postNotificationName:customDownloadTaskExeing object:nil userInfo:@{@"operation" : operation}];
  412. }
  413. }
  414. // 等待某一个operation 保存本地 通知外界
  415. - (void)operationSuccessWithOperation:(customDownloadOperation *)operation {
  416. HLog(@"DownloadTaskExeEnd \n %@",operation.url);
  417. operation.downloadState = customDownloadStateCompleted;
  418. [[NSNotificationCenter defaultCenter] postNotificationName:customDownloadTaskExeEnd object:nil userInfo:@{@"operation" : operation}];
  419. }
  420. -(NSURLSession*)creatNewSessionFun
  421. {
  422. NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
  423. // 设置请求超时
  424. config.timeoutIntervalForRequest = -1;
  425. // config.networkServiceType = NSURLNetworkServiceTypeVideo;
  426. config.timeoutIntervalForResource = -1;
  427. // config.TLSMaximumSupportedProtocol = kSSLProtocolAll;
  428. //_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue currentQueue]];
  429. NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
  430. return session;
  431. }
  432. #pragma mark - lazy load
  433. - (NSMutableArray *)downloadWaitingUrlArr {
  434. if (!_downloadWaitingUrlArr) {
  435. _downloadWaitingUrlArr = [NSMutableArray array];
  436. }
  437. return _downloadWaitingUrlArr;
  438. }
  439. - (NSMutableArray *)downloadingOperationArr {
  440. if (!_downloadingOperationArr) {
  441. _downloadingOperationArr = [NSMutableArray array];
  442. }
  443. return _downloadingOperationArr;
  444. }
  445. - (BOOL)isDownLoadIngType
  446. {
  447. if(self.downloadingOperationArr.count >= 1){
  448. return YES;
  449. }
  450. return NO;
  451. }
  452. @end