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