// // customDownloadManager.m // 双子星云手机 // // Created by xd h on 2024/7/1. // #import "customDownloadManager.h" #import "customDownloadOperation.h" #import "customDownloadCacheManager.h" @interface customDownloadManager () //排队等候下载的下载地址数组 @property(nonatomic,strong) NSMutableArray *downloadWaitingUrlArr; //正在下载的下载地址数组 @property(nonatomic,strong) NSMutableArray *downloadingOperationArr; @end @implementation customDownloadManager + (instancetype)shareManager { static customDownloadManager *_instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[self alloc] init]; }); return _instance; } - (instancetype)init { if (self = [super init]) { _maxDownLoadCount = 1; //[self registeNotification]; } return self; } - (NSString*)uid{ if(!_uid || _uid.length == 0){ return @"customUserName"; } return _uid; } /** 添加要下载的 网络连接 */ - (void)addDownloadWithURLs:(NSArray *)urls{ for (NSString *addUrl in urls) { BOOL needAddType = YES; //1. 排查下载中 for (customDownloadOperation *operationDoing in self.downloadingOperationArr) { if([operationDoing.url isEqualToString:addUrl]){ needAddType = NO; break; } } //1. 排查等待下载 for (NSString *waitUrl in self.downloadWaitingUrlArr) { if([waitUrl isEqualToString:addUrl]){ needAddType = NO; break; } } if(needAddType){ [self.downloadWaitingUrlArr addObject:addUrl]; } } //启动下载 [self beginDownload]; } //在添加下载地址后 启动下载 - (void)beginDownload { @synchronized (self) { if(self.downloadingOperationArr.count == _maxDownLoadCount){ HLog(@"正在下载的数量达到了最大值 %ld",_maxDownLoadCount) return; } if(self.downloadWaitingUrlArr.count == 0){ HLog(@"没有等待中的下载任务") return; } NSInteger canAddTaskNumber = _maxDownLoadCount - self.downloadingOperationArr.count; for (int i=0; i= 1){ //创建下载任务 NSString *downloadUrl = self.downloadWaitingUrlArr.firstObject; NSURLSession *session = [self creatNewSessionFun]; customDownloadOperation * operation = [[customDownloadOperation alloc] initWith:downloadUrl session:session]; //等待下载中的任务 [self.downloadWaitingUrlArr removeObjectAtIndex:0]; //添加到下载中数组 [self.downloadingOperationArr addObject:operation]; [operation.dataTask resume]; } } } } /** 开始任务(不会自动添加任务,列队中没有就直接返回) 后续改为会自动添加任务 */ - (void)startDownLoadWithUrl:(NSString *)url { BOOL needAddType = YES; //1. 排查下载中 for (customDownloadOperation *operationDoing in self.downloadingOperationArr) { if([operationDoing.url isEqualToString:url]){ needAddType = NO; break; } } //1. 排查等待下载 for (NSString *waitUrl in self.downloadWaitingUrlArr) { if([waitUrl isEqualToString:url]){ needAddType = NO; break; } } if(needAddType){ [self.downloadWaitingUrlArr addObject:url]; } //启动下载 [self beginDownload]; } /** 删除任务(删除下载url内容的任务) */ - (void)deleteDownloadWithUrl:(NSString *)url { //1.检测等待下载的任务 for (NSString*waitingUrl in self.downloadWaitingUrlArr) { if ([waitingUrl isEqualToString:url]) { [self.downloadWaitingUrlArr removeObject:waitingUrl]; break; } } //2.检测下载中的任务 for (customDownloadOperation *operationDoing in self.downloadingOperationArr) { if([operationDoing.url isEqualToString:url]){ [operationDoing.dataTask cancel]; // operationDoing.dataTask = nil; // operationDoing.session = nil; // // [operationDoing.handle closeFile]; // operationDoing.handle = nil; // // [self.downloadingOperationArr removeObject:operationDoing]; break; } } //3. 删除本地文件 [customDownloadCacheManager deleteFileWithUrl:url]; //4.进行下一个任务 //[self beginDownload]; } /** 暂停任务(暂停下载url内容的任务) */ - (void)supendDownloadWithUrl:(NSString *)url { //1.检测等待下载的任务 for (NSString*waitingUrl in self.downloadWaitingUrlArr) { if ([waitingUrl isEqualToString:url]) { [self.downloadWaitingUrlArr removeObject:waitingUrl]; break; } } //2.检测下载中的任务 for (customDownloadOperation *operationDoing in self.downloadingOperationArr) { if([operationDoing.url isEqualToString:url]){ operationDoing.isManualCancel = YES; if(operationDoing.dataTask){ [operationDoing.dataTask cancel]; } // operationDoing.dataTask = nil; // operationDoing.session = nil; // // [operationDoing.handle closeFile]; // operationDoing.handle = nil; // // //删除后 didCompleteWithError 不再处理 // [self.downloadingOperationArr removeObject:operationDoing]; break; } } //进行下一个任务 //[self beginDownload]; } /** 暂停当前所有的下载任务 下载任务不会从列队中删除 */ - (void)suspendAllDownloadTask { //1.删除等待下载的任务 [self.downloadWaitingUrlArr removeAllObjects]; //2.检测下载中的任务 for (customDownloadOperation *operationDoing in self.downloadingOperationArr) { operationDoing.isManualCancel = YES; if(operationDoing.dataTask){ [operationDoing.dataTask cancel]; } // operationDoing.dataTask = nil; // operationDoing.session = nil; // [operationDoing.handle closeFile]; // operationDoing.handle = nil; } [self.downloadingOperationArr removeAllObjects]; } #pragma mark 重新启动因为网络失败而停止的任务 - (void)reDownloadNetworkTaskBy:(NSString*)url { @synchronized (self) { [self.downloadWaitingUrlArr insertObject:url atIndex:0]; } [self beginDownload]; } #pragma mark - // ssl 服务 证书信任 - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{ if(![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"]) { return; } // 信任该插件 NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust]; // 第一个参数 告诉系统如何处置 completionHandler(NSURLSessionAuthChallengeUseCredential,credential); } //当请求协议是https的时候回调用该方法 //Challenge 挑战 质询(受保护空间) //NSURLAuthenticationMethodServerTrust 服务器信任证书 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler { if(![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"]) { return; } // 信任该插件 NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust]; // 第一个参数 告诉系统如何处置 completionHandler(NSURLSessionAuthChallengeUseCredential,credential); } // 接受到响应调用 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { // 将响应交给列队处理 BOOL canDownload = [self handleOperateby:dataTask WithResponse:response]; if(canDownload){ // 允许下载 completionHandler(NSURLSessionResponseAllow); } else{ // 不允许下载 completionHandler(NSURLSessionResponseCancel); } } // 接受到数据碎片 的时候调用,调用多次 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { // 接收到session 下载碎片交个列队管理 [self dataTask:dataTask didReceiveData:data]; } // 完成下载 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error { [self task:task didCompleteWithError:error]; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler { } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { } #pragma mark 处理接收到的数据 // 接收到相应时 - (BOOL)handleOperateby:(NSURLSessionTask*)dataTask WithResponse:(NSURLResponse *)response { customDownloadOperation *operation = nil; for (customDownloadOperation *operationDoing in self.downloadingOperationArr) { if(operationDoing.dataTask == dataTask){ operation = operationDoing; break; } } if (!operation) { HLog(@"没找到当前下载任务"); operation.downloadState = customDownloadStateFailed; [dataTask cancel]; return NO; } // 检查response是否是NSHTTPURLResponse的实例 if ([response isKindOfClass:[NSHTTPURLResponse class]]) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; NSInteger statusCode = httpResponse.statusCode; HLog(@"HTTP Status Code: %ld", (long)statusCode); if(statusCode == 404){ operation.downloadState = customDownloadStateFailed; operation.isFile404Cancel = YES; return NO; } } // 总的size if (operation.currentSize + response.expectedContentLength == 0) { HLog(@"下载数据回调异常"); operation.downloadState = customDownloadStateFailed; return NO; } operation.totalSize = operation.currentSize + response.expectedContentLength; // 创建空的文件夹 if (operation.currentSize == 0) { // 创建空的文件 [[NSFileManager defaultManager] createFileAtPath:operation.fullPath contents:nil attributes:nil]; } // 创建文件句柄 operation.handle = [NSFileHandle fileHandleForWritingAtPath:operation.fullPath]; // 文件句柄移动到文件末尾 位置 // 返回值是 unsign long long [operation.handle seekToEndOfFile]; // 开始下载记录文件下载信息 operation.downloadState = customDownloadStateDoing; return YES; } - (void)dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { customDownloadOperation *operation = nil; for (customDownloadOperation *operationDoing in self.downloadingOperationArr) { if(operationDoing.dataTask == dataTask){ operation = operationDoing; break; } } if (!operation) { HLog(@"没找到当前下载任务"); [dataTask cancel]; return; } //手动取消任务 if (operation.isManualCancel) { return; } // 获得已经下载的文件大小 operation.currentSize += data.length; HLog(@"currentSize:%lld---progress:%.2f", operation.currentSize, 1.00*operation.currentSize/operation.totalSize); // 写入文件 if(operation.handle){ [operation.handle writeData:data]; } // 下载中通知 [self operationDoningWithOperation:operation]; } - (void)task:(NSURLSessionTask *)dataTask didCompleteWithError:(NSError *)error { customDownloadOperation *operation = nil; for (customDownloadOperation *operationDoing in self.downloadingOperationArr) { if(operationDoing.dataTask == dataTask){ operation = operationDoing; [self.downloadingOperationArr removeObject:operationDoing]; break; } } if (!operation) { HLog(@"没找到当前下载任务"); //[dataTask suspend]; return; } if(operation.dataTask){ //[operation.dataTask cancel]; operation.dataTask = nil; operation.session = nil; if(operation.handle){ // 关闭文件句柄 [operation.handle closeFile]; // 释放文件句柄 operation.handle = nil; } } if(error && (error.code == -1005 || error.code == -1009) && !operation.isFile404Cancel){//网络中断 HLog(@"reDownloadNetworkTaskBy"); //延时几秒再次启动这个任务 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self reDownloadNetworkTaskBy:operation.url]; }); return; } if(operation.isManualCancel){ [self beginDownload]; return; } // 完成下载 通知 block if (error ||operation.downloadState == customDownloadStateFailed) { [self operationFailedWithOperation:operation]; } else { operation.isFinished = YES; operation.timeStamp = [iTools getNowTimeStamp]; [self operationSuccessWithOperation:operation]; } [self beginDownload]; } #pragma mark 发送通知 // 失败某一个operation 保存本地 通知外界 - (void)operationFailedWithOperation:(customDownloadOperation *)operation { HLog(@"DownloadTaskExeError \n %@",operation.url); operation.downloadState = customDownloadStateFailed; [[NSNotificationCenter defaultCenter] postNotificationName:customDownloadTaskExeError object:nil userInfo:@{@"operation" : operation}]; } // 重启某一个operation 保存本地 通知外界 - (void)operationDoningWithOperation:(customDownloadOperation *)operation { HLog(@"DownloadTaskExeing"); NSTimeInterval curTime = [[NSDate date] timeIntervalSince1970]; NSTimeInterval timeDiff = curTime - operation.preNotTimeInterval; HLog(@"控制刷新时间为1秒:%f",timeDiff); CGFloat RefreshTimer = 0.8; if(operation.totalSize <= 5*1024*1024){//小于5M RefreshTimer = 0.2; } if (operation.preNotTimeInterval <= 0 || timeDiff > RefreshTimer ) { operation.preNotTimeInterval = curTime; [[NSNotificationCenter defaultCenter] postNotificationName:customDownloadTaskExeing object:nil userInfo:@{@"operation" : operation}]; } } // 等待某一个operation 保存本地 通知外界 - (void)operationSuccessWithOperation:(customDownloadOperation *)operation { HLog(@"DownloadTaskExeEnd \n %@",operation.url); operation.downloadState = customDownloadStateCompleted; [[NSNotificationCenter defaultCenter] postNotificationName:customDownloadTaskExeEnd object:nil userInfo:@{@"operation" : operation}]; } -(NSURLSession*)creatNewSessionFun { NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; // 设置请求超时 config.timeoutIntervalForRequest = -1; // config.networkServiceType = NSURLNetworkServiceTypeVideo; config.timeoutIntervalForResource = -1; // config.TLSMaximumSupportedProtocol = kSSLProtocolAll; //_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue currentQueue]]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; return session; } #pragma mark - lazy load - (NSMutableArray *)downloadWaitingUrlArr { if (!_downloadWaitingUrlArr) { _downloadWaitingUrlArr = [NSMutableArray array]; } return _downloadWaitingUrlArr; } - (NSMutableArray *)downloadingOperationArr { if (!_downloadingOperationArr) { _downloadingOperationArr = [NSMutableArray array]; } return _downloadingOperationArr; } - (BOOL)isDownLoadIngType { if(self.downloadingOperationArr.count >= 1){ return YES; } return NO; } @end