EMASCurlWeb/EMASCurlWebDataTask.m (188 lines of code) (raw):

// // EMASCurlWebDataTask.m // EMASCurl // // Created by xuyecan on 2025/2/5. // #import "EMASCurlWebDataTask.h" #import "EMASCurlWebUtils.h" #import "EMASCurlWebLogger.h" #import "NSCachedURLResponse+EMASCurl.h" NSInteger const kEMASCurlGetRequestRetryLimit = 0; @interface EMASCurlNetworkDataTask () @property (nonatomic, assign) BOOL isCancelled; @property (nonatomic, assign) RequestTaskIdentifier requestID; @property (nonatomic, strong) NSHTTPURLResponse *receivedResponse; @property (nonatomic, strong) NSMutableData *receivedData; @end @implementation EMASCurlNetworkDataTask - (instancetype)initWithRequest:(NSURLRequest *)request { self = [super init]; if (self) { _originalRequest = request; _currentRetryCount = 0; _isCancelled = NO; _canCache = YES; _requestID = -1; _receivedData = [NSMutableData data]; } return self; } #pragma mark - Public Methods - (void)resume { if (self.isCancelled) { return; } // 检查是否可用缓存 if (![self prepareRequestAndTryCache]) { return; } // 设置请求优先级对应的超时时间 NSMutableURLRequest *mutableRequest = [self.originalRequest mutableCopy]; // 强制不使用系统缓存 mutableRequest.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; self.originalRequest = [mutableRequest copy]; // 发起网络请求 EMASCurlWeak(self) self.requestID = [self.networkManagerWeakRef startWithRequest:self.originalRequest responseCallback:^(NSURLResponse * _Nonnull response) { EMASCurlStrong(self) [self handleResponse:response]; } dataCallback:^(NSData * _Nonnull data) { EMASCurlStrong(self) [self handleData:data]; } successCallback:^{ EMASCurlStrong(self) [self handleSuccess]; } failCallback:^(NSError * _Nonnull error) { EMASCurlStrong(self) [self handleFailure:error]; } redirectCallback:^(NSURLResponse * _Nonnull resp, NSURLRequest * _Nonnull newReq, EMASCurlNetRedirectDecisionCallback decisionCb) { EMASCurlStrong(self) [self handleRedirect:resp newRequest:newReq decisionCallback:decisionCb]; }]; } - (void)cancel { if (self.isCancelled) { return; } self.isCancelled = YES; [self.networkManagerWeakRef cancelWithRequestIdentifier:self.requestID]; if (self.cancelHandler) { self.cancelHandler(); } } #pragma mark - Cache & Request Preparation // 检查缓存是否可用并尝试直接返回缓存内容 - (BOOL)prepareRequestAndTryCache { // 不使用缓存的情况 BOOL isGetMethod = [[self.originalRequest.HTTPMethod uppercaseString] isEqualToString:@"GET"]; if (!isGetMethod) { self.canCache = NO; } NSString *mainURL = self.originalRequest.mainDocumentURL.absoluteString; NSString *requestURL = self.originalRequest.URL.absoluteString; if ([EMASCurlWebUtils isValidStr:mainURL] && [EMASCurlWebUtils isEqualURLA:requestURL withURLB:mainURL]) { self.canCache = NO; } if (!self.canCache) { return YES; } // 有缓存能力则获取缓存 NSCachedURLResponse *cachedResponse = [self.httpCacheWeakRef getCachedResponseWithRequest:self.originalRequest]; if (cachedResponse && ![cachedResponse emas_isExpired]) { if (self.responseCallback) { self.responseCallback(cachedResponse.response); } if (self.dataCallback) { self.dataCallback(cachedResponse.data); } if (self.successCallback) { self.successCallback(); } return NO; } // 如果缓存已过期,则设置If-None-Match/If-Modified-Since if (cachedResponse && [cachedResponse emas_isExpired]) { NSMutableURLRequest *tmpRequest = [self.originalRequest mutableCopy]; if (cachedResponse.emas_etag) { [tmpRequest setValue:cachedResponse.emas_etag forHTTPHeaderField:@"If-None-Match"]; } if (cachedResponse.emas_lastModified) { [tmpRequest setValue:cachedResponse.emas_lastModified forHTTPHeaderField:@"If-Modified-Since"]; } self.originalRequest = [tmpRequest copy]; } return YES; } #pragma mark - Callback Handling // 处理响应回调 - (void)handleResponse:(NSURLResponse *)response { if (!self.canCache || ![response isKindOfClass:[NSHTTPURLResponse class]]) { if (self.responseCallback) { self.responseCallback(response); } return; } self.receivedResponse = (NSHTTPURLResponse *)response; // 如果 304 不立即回调 responseCallback,等 successCallback 时再处理 if (self.receivedResponse.statusCode != 304 || ![self.httpCacheWeakRef getCachedResponseWithRequest:self.originalRequest]) { if (self.responseCallback) { self.responseCallback(response); } } } // 处理数据回调 - (void)handleData:(NSData *)data { [self.receivedData appendData:data]; if (self.dataCallback) { self.dataCallback(data); } } // 处理成功回调 - (void)handleSuccess { if (!self.canCache) { if (self.successCallback) { self.successCallback(); } return; } // 如果 304, 则尝试使用缓存 if (self.receivedResponse.statusCode == 304 && [self.httpCacheWeakRef getCachedResponseWithRequest:self.originalRequest]) { NSCachedURLResponse *cachedResponse = [self.httpCacheWeakRef updateCachedResponseWithURLResponse:self.receivedResponse request:self.originalRequest]; if (self.responseCallback) { self.responseCallback(cachedResponse.response); } if (self.dataCallback) { self.dataCallback(cachedResponse.data); } if (self.successCallback) { self.successCallback(); } return; } [self.httpCacheWeakRef cacheWithHTTPURLResponse:self.receivedResponse data:self.receivedData request:self.originalRequest]; if (self.successCallback) { self.successCallback(); } } // 处理失败回调 - (void)handleFailure:(NSError *)error { // 重试条件:超时错误且为GET请求,并且未超出重试次数 BOOL isTimeout = (error.code == -1001); BOOL isGetMethod = [[self.originalRequest.HTTPMethod uppercaseString] isEqualToString:@"GET"]; if (isTimeout && isGetMethod && self.currentRetryCount < kEMASCurlGetRequestRetryLimit) { EMASCurlCacheLog(@"Request failed and we retry, url: %@, error: %@", self.originalRequest.URL.absoluteString, error); [self performRetry]; return; } if (self.failCallback) { EMASCurlCacheLog(@"Request failed and we give up, url: %@, error: %@", self.originalRequest.URL.absoluteString, error); self.failCallback(error); } } // 处理重定向回调 - (void)handleRedirect:(NSURLResponse *)response newRequest:(NSURLRequest *)redirectRequest decisionCallback:(EMASCurlNetRedirectDecisionCallback)decisionCallback { // 直接透传给上层 if (self.redirectCallback) { self.redirectCallback(response, redirectRequest, decisionCallback); } else { decisionCallback(YES); } } #pragma mark - Internal Retry - (void)performRetry { self.currentRetryCount++; // 先取消当前任务 [self cancel]; if (self.retryHandler) { self.retryHandler(); } } @end