fix redundant url requests

This commit is contained in:
马遥 2023-07-26 15:23:59 +08:00
parent 8dbf8ed97a
commit 39af2be6ee
2 changed files with 95 additions and 84 deletions

View File

@ -21,22 +21,6 @@ NSNotificationName const SDWebImageDownloadStopNotification = @"SDWebImageDownlo
NSNotificationName const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification"; NSNotificationName const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
static void * SDWebImageDownloaderOperationKey = &SDWebImageDownloaderOperationKey;
BOOL SDWebImageDownloaderOperationGetCompleted(id<SDWebImageDownloaderOperation> operation) {
NSCParameterAssert(operation);
NSNumber *value = objc_getAssociatedObject(operation, SDWebImageDownloaderOperationKey);
if (value != nil) {
return value.boolValue;
} else {
return NO;
}
}
void SDWebImageDownloaderOperationSetCompleted(id<SDWebImageDownloaderOperation> operation, BOOL isCompleted) {
NSCParameterAssert(operation);
objc_setAssociatedObject(operation, SDWebImageDownloaderOperationKey, @(isCompleted), OBJC_ASSOCIATION_RETAIN);
}
@interface SDWebImageDownloadToken () @interface SDWebImageDownloadToken ()
@ -239,7 +223,7 @@ void SDWebImageDownloaderOperationSetCompleted(id<SDWebImageDownloaderOperation>
BOOL shouldNotReuseOperation; BOOL shouldNotReuseOperation;
if (operation) { if (operation) {
@synchronized (operation) { @synchronized (operation) {
shouldNotReuseOperation = operation.isFinished || operation.isCancelled || SDWebImageDownloaderOperationGetCompleted(operation); shouldNotReuseOperation = operation.isFinished || operation.isCancelled;
} }
} else { } else {
shouldNotReuseOperation = YES; shouldNotReuseOperation = YES;
@ -518,12 +502,6 @@ didReceiveResponse:(NSURLResponse *)response
// Identify the operation that runs this task and pass it the delegate method // Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task]; NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if (dataOperation) {
@synchronized (dataOperation) {
// Mark the downloader operation `isCompleted = YES`, no longer re-use this operation when new request comes in
SDWebImageDownloaderOperationSetCompleted(dataOperation, YES);
}
}
if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) { if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[dataOperation URLSession:session task:task didCompleteWithError:error]; [dataOperation URLSession:session task:task didCompleteWithError:error];
} }

View File

@ -14,8 +14,6 @@
#import "SDImageCacheDefine.h" #import "SDImageCacheDefine.h"
#import "SDCallbackQueue.h" #import "SDCallbackQueue.h"
BOOL SDWebImageDownloaderOperationGetCompleted(id<SDWebImageDownloaderOperation> operation); // Private currently, mark open if needed
// A handler to represent individual request // A handler to represent individual request
@interface SDWebImageDownloaderOperationToken : NSObject @interface SDWebImageDownloaderOperationToken : NSObject
@ -62,6 +60,8 @@ BOOL SDWebImageDownloaderOperationGetCompleted(id<SDWebImageDownloaderOperation>
@property (strong, nonatomic, nullable) NSError *responseError; @property (strong, nonatomic, nullable) NSError *responseError;
@property (assign, nonatomic) double previousProgress; // previous progress percent @property (assign, nonatomic) double previousProgress; // previous progress percent
@property (assign, nonatomic, getter = isDownloadCompleted) BOOL downloadCompleted;
@property (strong, nonatomic, nullable) id<SDWebImageDownloaderResponseModifier> responseModifier; // modify original URLResponse @property (strong, nonatomic, nullable) id<SDWebImageDownloaderResponseModifier> responseModifier; // modify original URLResponse
@property (strong, nonatomic, nullable) id<SDWebImageDownloaderDecryptor> decryptor; // decrypt image data @property (strong, nonatomic, nullable) id<SDWebImageDownloaderDecryptor> decryptor; // decrypt image data
@ -112,6 +112,7 @@ BOOL SDWebImageDownloaderOperationGetCompleted(id<SDWebImageDownloaderOperation>
_finished = NO; _finished = NO;
_expectedSize = 0; _expectedSize = 0;
_unownedSession = session; _unownedSession = session;
_downloadCompleted = NO;
_coderQueue = [[NSOperationQueue alloc] init]; _coderQueue = [[NSOperationQueue alloc] init];
_coderQueue.maxConcurrentOperationCount = 1; _coderQueue.maxConcurrentOperationCount = 1;
_coderQueue.name = @"com.hackemist.SDWebImageDownloaderOperation.coderQueue"; _coderQueue.name = @"com.hackemist.SDWebImageDownloaderOperation.coderQueue";
@ -338,6 +339,91 @@ BOOL SDWebImageDownloaderOperationGetCompleted(id<SDWebImageDownloaderOperation>
return YES; return YES;
} }
// Check for unprocessed tokens.
// if all tokens have been processed call [self done].
- (void)checkDoneWithImageData:(NSData *)imageData
finisdTokens:(NSArray<SDWebImageDownloaderOperationToken *> *)finisdTokens {
NSMutableArray<SDWebImageDownloaderOperationToken *> *tokens;
@synchronized (self) {
tokens = [self.callbackTokens mutableCopy];
}
[finisdTokens enumerateObjectsUsingBlock:^(SDWebImageDownloaderOperationToken * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[tokens removeObjectIdenticalTo:obj];
}];
if (tokens.count == 0) {
[self done];
} else {
// If there are new tokens added during the decoding operation, the decoding operation is supplemented with these new tokens.
[self startCoderOperationWithImageData:imageData pendingTokens:tokens finishedTokens:finisdTokens];
}
}
- (void)startCoderOperationWithImageData:(NSData *)imageData
pendingTokens:(NSArray<SDWebImageDownloaderOperationToken *> *)pendingTokens
finishedTokens:(NSArray<SDWebImageDownloaderOperationToken *> *)finishedTokens {
@weakify(self);
for (SDWebImageDownloaderOperationToken *token in pendingTokens) {
[self.coderQueue addOperationWithBlock:^{
@strongify(self);
if (!self) {
return;
}
UIImage *image;
// check if we already decode this variant of image for current callback
if (token.decodeOptions) {
image = [self.imageMap objectForKey:token.decodeOptions];
}
if (!image) {
// check if we already use progressive decoding, use that to produce faster decoding
id<SDProgressiveImageCoder> progressiveCoder = SDImageLoaderGetProgressiveCoder(self);
SDWebImageOptions options = [[self class] imageOptionsFromDownloaderOptions:self.options];
SDWebImageContext *context;
if (token.decodeOptions) {
SDWebImageMutableContext *mutableContext = [NSMutableDictionary dictionaryWithDictionary:self.context];
SDSetDecodeOptionsToContext(mutableContext, &options, token.decodeOptions);
context = [mutableContext copy];
} else {
context = self.context;
}
if (progressiveCoder) {
image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, YES, self, options, context);
} else {
image = SDImageLoaderDecodeImageData(imageData, self.request.URL, options, context);
}
if (image && token.decodeOptions) {
[self.imageMap setObject:image forKey:token.decodeOptions];
}
}
CGSize imageSize = image.size;
if (imageSize.width == 0 || imageSize.height == 0) {
NSString *description = image == nil ? @"Downloaded image decode failed" : @"Downloaded image has 0 pixels";
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : description}];
[self callCompletionBlockWithToken:token image:nil imageData:nil error:error finished:YES];
} else {
[self callCompletionBlockWithToken:token image:image imageData:imageData error:nil finished:YES];
}
}];
}
// call [self done] after all completed block was dispatched
dispatch_block_t doneBlock = ^{
@strongify(self);
if (!self) {
return;
}
// Check for new tokens added during the decode operation.
[self checkDoneWithImageData:imageData
finisdTokens:[finishedTokens arrayByAddingObjectsFromArray:pendingTokens]];
};
if (@available(iOS 13, tvOS 13, macOS 10.15, watchOS 6, *)) {
// seems faster than `addOperationWithBlock`
[self.coderQueue addBarrierBlock:doneBlock];
} else {
// serial queue, this does the same effect in semantics
[self.coderQueue addOperationWithBlock:doneBlock];
}
}
#pragma mark NSURLSessionDataDelegate #pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session - (void)URLSession:(NSURLSession *)session
@ -478,7 +564,7 @@ didReceiveResponse:(NSURLResponse *)response
} }
// When cancelled or transfer finished (`didCompleteWithError`), cancel the progress callback, only completed block is called and enough // When cancelled or transfer finished (`didCompleteWithError`), cancel the progress callback, only completed block is called and enough
@synchronized (self) { @synchronized (self) {
if (self.isCancelled || SDWebImageDownloaderOperationGetCompleted(self)) { if (self.isCancelled || self.isDownloadCompleted) {
return; return;
} }
} }
@ -521,6 +607,8 @@ didReceiveResponse:(NSURLResponse *)response
// If we already cancel the operation or anything mark the operation finished, don't callback twice // If we already cancel the operation or anything mark the operation finished, don't callback twice
if (self.isFinished) return; if (self.isFinished) return;
self.downloadCompleted = YES;
NSArray<SDWebImageDownloaderOperationToken *> *tokens; NSArray<SDWebImageDownloaderOperationToken *> *tokens;
@synchronized (self) { @synchronized (self) {
tokens = [self.callbackTokens copy]; tokens = [self.callbackTokens copy];
@ -565,64 +653,9 @@ didReceiveResponse:(NSURLResponse *)response
} else { } else {
// decode the image in coder queue, cancel all previous decoding process // decode the image in coder queue, cancel all previous decoding process
[self.coderQueue cancelAllOperations]; [self.coderQueue cancelAllOperations];
@weakify(self); [self startCoderOperationWithImageData:imageData
for (SDWebImageDownloaderOperationToken *token in tokens) { pendingTokens:tokens
[self.coderQueue addOperationWithBlock:^{ finishedTokens:@[]];
@strongify(self);
if (!self) {
return;
}
UIImage *image;
// check if we already decode this variant of image for current callback
if (token.decodeOptions) {
image = [self.imageMap objectForKey:token.decodeOptions];
}
if (!image) {
// check if we already use progressive decoding, use that to produce faster decoding
id<SDProgressiveImageCoder> progressiveCoder = SDImageLoaderGetProgressiveCoder(self);
SDWebImageOptions options = [[self class] imageOptionsFromDownloaderOptions:self.options];
SDWebImageContext *context;
if (token.decodeOptions) {
SDWebImageMutableContext *mutableContext = [NSMutableDictionary dictionaryWithDictionary:self.context];
SDSetDecodeOptionsToContext(mutableContext, &options, token.decodeOptions);
context = [mutableContext copy];
} else {
context = self.context;
}
if (progressiveCoder) {
image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, YES, self, options, context);
} else {
image = SDImageLoaderDecodeImageData(imageData, self.request.URL, options, context);
}
if (image && token.decodeOptions) {
[self.imageMap setObject:image forKey:token.decodeOptions];
}
}
CGSize imageSize = image.size;
if (imageSize.width == 0 || imageSize.height == 0) {
NSString *description = image == nil ? @"Downloaded image decode failed" : @"Downloaded image has 0 pixels";
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : description}];
[self callCompletionBlockWithToken:token image:nil imageData:nil error:error finished:YES];
} else {
[self callCompletionBlockWithToken:token image:image imageData:imageData error:nil finished:YES];
}
}];
}
// call [self done] after all completed block was dispatched
dispatch_block_t doneBlock = ^{
@strongify(self);
if (!self) {
return;
}
[self done];
};
if (@available(iOS 13, tvOS 13, macOS 10.15, watchOS 6, *)) {
// seems faster than `addOperationWithBlock`
[self.coderQueue addBarrierBlock:doneBlock];
} else {
// serial queue, this does the same effect in semantics
[self.coderQueue addOperationWithBlock:doneBlock];
}
} }
} else { } else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]]; [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];