Merge pull request #3429 from dreampiggy/perf/remove_conforms_to_protocol

Replace conformsToProtocol call with appropriate respondsToSelector check to improve performance
This commit is contained in:
DreamPiggy 2022-11-08 21:44:07 +08:00 committed by GitHub
commit 6ce59aa8c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 51 additions and 77 deletions

View File

@ -471,7 +471,7 @@
{ {
if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)] && image.sd_isIncremental && [image respondsToSelector:@selector(animatedCoder)]) { if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)] && image.sd_isIncremental && [image respondsToSelector:@selector(animatedCoder)]) {
id<SDAnimatedImageCoder> animatedCoder = [(id<SDAnimatedImage>)image animatedCoder]; id<SDAnimatedImageCoder> animatedCoder = [(id<SDAnimatedImage>)image animatedCoder];
if ([animatedCoder conformsToProtocol:@protocol(SDProgressiveImageCoder)]) { if ([animatedCoder respondsToSelector:@selector(initIncrementalWithOptions:)]) {
return (id<SDAnimatedImageCoder, SDProgressiveImageCoder>)animatedCoder; return (id<SDAnimatedImageCoder, SDProgressiveImageCoder>)animatedCoder;
} }
} }

View File

@ -245,7 +245,7 @@ static NSString * _defaultDiskCacheDirectory;
dispatch_async(self.ioQueue, ^{ dispatch_async(self.ioQueue, ^{
@autoreleasepool { @autoreleasepool {
NSData *data = imageData; NSData *data = imageData;
if (!data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) { if (!data && [image respondsToSelector:@selector(animatedImageData)]) {
// If image is custom animated image class, prefer its original animated data // If image is custom animated image class, prefer its original animated data
data = [((id<SDAnimatedImage>)image) animatedImageData]; data = [((id<SDAnimatedImage>)image) animatedImageData];
} }
@ -440,8 +440,7 @@ static NSString * _defaultDiskCacheDirectory;
if (image) { if (image) {
if (options & SDImageCacheDecodeFirstFrameOnly) { if (options & SDImageCacheDecodeFirstFrameOnly) {
// Ensure static image // Ensure static image
Class animatedImageClass = image.class; if (image.sd_isAnimated) {
if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
#if SD_MAC #if SD_MAC
image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp]; image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else #else
@ -574,8 +573,7 @@ static NSString * _defaultDiskCacheDirectory;
if (image) { if (image) {
if (options & SDImageCacheDecodeFirstFrameOnly) { if (options & SDImageCacheDecodeFirstFrameOnly) {
// Ensure static image // Ensure static image
Class animatedImageClass = image.class; if (image.sd_isAnimated) {
if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
#if SD_MAC #if SD_MAC
image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp]; image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else #else

View File

@ -94,10 +94,8 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue]; CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue];
// Grab the image coder // Grab the image coder
id<SDImageCoder> imageCoder; id<SDImageCoder> imageCoder = context[SDWebImageContextImageCoder];
if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) { if (!imageCoder) {
imageCoder = context[SDWebImageContextImageCoder];
} else {
imageCoder = [SDImageCodersManager sharedManager]; imageCoder = [SDImageCodersManager sharedManager];
} }
@ -128,12 +126,6 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
if (lazyDecode) { if (lazyDecode) {
// lazyDecode = NO means we should not forceDecode, highest priority // lazyDecode = NO means we should not forceDecode, highest priority
shouldDecode = NO; shouldDecode = NO;
} else if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) {
// `SDAnimatedImage` do not decode
shouldDecode = NO;
} else if (image.sd_isAnimated) {
// animated image do not decode
shouldDecode = NO;
} }
if (shouldDecode) { if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image]; image = [SDImageCoderHelper decodedImageWithImage:image];

View File

@ -23,8 +23,8 @@ static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizati
// Specify File Size for lossy format encoding, like JPEG // Specify File Size for lossy format encoding, like JPEG
static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize"; static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
// Only assert on Debug mode and Simulator // Only assert on Debug mode
#define SD_CHECK_CGIMAGE_RETAIN_SOURCE DEBUG && TARGET_OS_SIMULATOR && \ #define SD_CHECK_CGIMAGE_RETAIN_SOURCE DEBUG && \
((__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0)) || \ ((__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0)) || \
((__TV_OS_VERSION_MAX_ALLOWED >= __TVOS_15_0)) ((__TV_OS_VERSION_MAX_ALLOWED >= __TVOS_15_0))
@ -300,10 +300,12 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
} }
} }
#if SD_CHECK_CGIMAGE_RETAIN_SOURCE #if SD_CHECK_CGIMAGE_RETAIN_SOURCE
// Assert here to check CGImageRef should not retain the CGImageSourceRef and has possible thread-safe issue (this is behavior on iOS 15+) if (@available(iOS 15, tvOS 15, *)) {
// If assert hit, fire issue to https://github.com/SDWebImage/SDWebImage/issues and we update the condition for this behavior check // Assert here to check CGImageRef should not retain the CGImageSourceRef and has possible thread-safe issue (this is behavior on iOS 15+)
extern CGImageSourceRef CGImageGetImageSource(CGImageRef); // If assert hit, fire issue to https://github.com/SDWebImage/SDWebImage/issues and we update the condition for this behavior check
NSCAssert(!CGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock"); extern CGImageSourceRef CGImageGetImageSource(CGImageRef);
NSCAssert(!CGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock");
}
#endif #endif
#if SD_UIKIT || SD_WATCH #if SD_UIKIT || SD_WATCH

View File

@ -47,10 +47,8 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue]; CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue];
// Grab the image coder // Grab the image coder
id<SDImageCoder> imageCoder; id<SDImageCoder> imageCoder = context[SDWebImageContextImageCoder];
if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) { if (!imageCoder) {
imageCoder = context[SDWebImageContextImageCoder];
} else {
imageCoder = [SDImageCodersManager sharedManager]; imageCoder = [SDImageCodersManager sharedManager];
} }
@ -81,12 +79,6 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
if (lazyDecode) { if (lazyDecode) {
// lazyDecode = NO means we should not forceDecode, highest priority // lazyDecode = NO means we should not forceDecode, highest priority
shouldDecode = NO; shouldDecode = NO;
} else if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) {
// `SDAnimatedImage` do not decode
shouldDecode = NO;
} else if (image.sd_isAnimated) {
// animated image do not decode
shouldDecode = NO;
} }
if (shouldDecode) { if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image]; image = [SDImageCoderHelper decodedImageWithImage:image];
@ -120,7 +112,7 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
if (!progressiveCoder) { if (!progressiveCoder) {
id<SDProgressiveImageCoder> imageCoder = context[SDWebImageContextImageCoder]; id<SDProgressiveImageCoder> imageCoder = context[SDWebImageContextImageCoder];
// Check the progressive coder if provided // Check the progressive coder if provided
if ([imageCoder conformsToProtocol:@protocol(SDProgressiveImageCoder)]) { if ([imageCoder respondsToSelector:@selector(initIncrementalWithOptions:)]) {
progressiveCoder = [[[imageCoder class] alloc] initIncrementalWithOptions:coderOptions]; progressiveCoder = [[[imageCoder class] alloc] initIncrementalWithOptions:coderOptions];
} else { } else {
// We need to create a new instance for progressive decoding to avoid conflicts // We need to create a new instance for progressive decoding to avoid conflicts
@ -143,7 +135,7 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
if (!decodeFirstFrame) { if (!decodeFirstFrame) {
// check whether we should use `SDAnimatedImage` // check whether we should use `SDAnimatedImage`
Class animatedImageClass = context[SDWebImageContextAnimatedImageClass]; Class animatedImageClass = context[SDWebImageContextAnimatedImageClass];
if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)] && [progressiveCoder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) { if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)] && [progressiveCoder respondsToSelector:@selector(animatedImageFrameAtIndex:)]) {
image = [[animatedImageClass alloc] initWithAnimatedCoder:(id<SDAnimatedImageCoder>)progressiveCoder scale:scale]; image = [[animatedImageClass alloc] initWithAnimatedCoder:(id<SDAnimatedImageCoder>)progressiveCoder scale:scale];
if (image) { if (image) {
// Progressive decoding does not preload frames // Progressive decoding does not preload frames
@ -164,12 +156,6 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
if (lazyDecode) { if (lazyDecode) {
// lazyDecode = NO means we should not forceDecode, highest priority // lazyDecode = NO means we should not forceDecode, highest priority
shouldDecode = NO; shouldDecode = NO;
} else if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) {
// `SDAnimatedImage` do not decode
shouldDecode = NO;
} else if (image.sd_isAnimated) {
// animated image do not decode
shouldDecode = NO;
} }
if (shouldDecode) { if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image]; image = [SDImageCoderHelper decodedImageWithImage:image];

View File

@ -353,9 +353,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
// Operation Class // Operation Class
Class operationClass = self.config.operationClass; Class operationClass = self.config.operationClass;
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) { if (!operationClass) {
// Custom operation class
} else {
operationClass = [SDWebImageDownloaderOperation class]; operationClass = [SDWebImageDownloaderOperation class];
} }
NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context]; NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];

View File

@ -7,6 +7,7 @@
*/ */
#import "SDWebImageDownloaderConfig.h" #import "SDWebImageDownloaderConfig.h"
#import "SDWebImageDownloaderOperation.h"
static SDWebImageDownloaderConfig * _defaultDownloaderConfig; static SDWebImageDownloaderConfig * _defaultDownloaderConfig;
@ -48,5 +49,12 @@ static SDWebImageDownloaderConfig * _defaultDownloaderConfig;
return config; return config;
} }
- (void)setOperationClass:(Class)operationClass {
if (operationClass) {
NSAssert([operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)], @"Custom downloader operation class must subclass NSOperation and conform to `SDWebImageDownloaderOperation` protocol");
}
_operationClass = operationClass;
}
@end @end

View File

@ -170,7 +170,7 @@ static id<SDImageLoader> _defaultImageLoader;
id<SDImageTransformer> transformer = self.transformer; id<SDImageTransformer> transformer = self.transformer;
if (context[SDWebImageContextImageTransformer]) { if (context[SDWebImageContextImageTransformer]) {
transformer = context[SDWebImageContextImageTransformer]; transformer = context[SDWebImageContextImageTransformer];
if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) { if ([transformer isEqual:NSNull.null]) {
transformer = nil; transformer = nil;
} }
} }
@ -286,10 +286,8 @@ static id<SDImageLoader> _defaultImageLoader;
progress:(nullable SDImageLoaderProgressBlock)progressBlock progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock { completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image cache to use // Grab the image cache to use
id<SDImageCache> imageCache; id<SDImageCache> imageCache = context[SDWebImageContextImageCache];
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { if (!imageCache) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache; imageCache = self.imageCache;
} }
// Get the query cache type // Get the query cache type
@ -338,14 +336,11 @@ static id<SDImageLoader> _defaultImageLoader;
progress:(nullable SDImageLoaderProgressBlock)progressBlock progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock { completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image cache to use, choose standalone original cache firstly // Grab the image cache to use, choose standalone original cache firstly
id<SDImageCache> imageCache; id<SDImageCache> imageCache = context[SDWebImageContextOriginalImageCache];
if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) { if (!imageCache) {
imageCache = context[SDWebImageContextOriginalImageCache];
} else {
// if no standalone cache available, use default cache // if no standalone cache available, use default cache
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { imageCache = context[SDWebImageContextImageCache];
imageCache = context[SDWebImageContextImageCache]; if (!imageCache) {
} else {
imageCache = self.imageCache; imageCache = self.imageCache;
} }
} }
@ -401,10 +396,8 @@ static id<SDImageLoader> _defaultImageLoader;
} }
// Grab the image loader to use // Grab the image loader to use
id<SDImageLoader> imageLoader; id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader];
if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) { if (!imageLoader) {
imageLoader = context[SDWebImageContextImageLoader];
} else {
imageLoader = self.imageLoader; imageLoader = self.imageLoader;
} }
@ -488,7 +481,7 @@ static id<SDImageLoader> _defaultImageLoader;
finished:(BOOL)finished finished:(BOOL)finished
completed:(nullable SDInternalCompletionBlock)completedBlock { completed:(nullable SDInternalCompletionBlock)completedBlock {
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer]; id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) { if ([transformer isEqual:NSNull.null]) {
transformer = nil; transformer = nil;
} }
// transformer check // transformer check
@ -546,14 +539,11 @@ static id<SDImageLoader> _defaultImageLoader;
finished:(BOOL)finished finished:(BOOL)finished
completed:(nullable SDInternalCompletionBlock)completedBlock { completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image cache to use, choose standalone original cache firstly // Grab the image cache to use, choose standalone original cache firstly
id<SDImageCache> imageCache; id<SDImageCache> imageCache = context[SDWebImageContextOriginalImageCache];
if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) { if (!imageCache) {
imageCache = context[SDWebImageContextOriginalImageCache];
} else {
// if no standalone cache available, use default cache // if no standalone cache available, use default cache
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { imageCache = context[SDWebImageContextImageCache];
imageCache = context[SDWebImageContextImageCache]; if (!imageCache) {
} else {
imageCache = self.imageCache; imageCache = self.imageCache;
} }
} }
@ -605,10 +595,8 @@ static id<SDImageLoader> _defaultImageLoader;
finished:(BOOL)finished finished:(BOOL)finished
completed:(nullable SDInternalCompletionBlock)completedBlock { completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image cache to use // Grab the image cache to use
id<SDImageCache> imageCache; id<SDImageCache> imageCache = context[SDWebImageContextImageCache];
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { if (!imageCache) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache; imageCache = self.imageCache;
} }
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache); BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
@ -704,10 +692,8 @@ static id<SDImageLoader> _defaultImageLoader;
error:(nonnull NSError *)error error:(nonnull NSError *)error
options:(SDWebImageOptions)options options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context { context:(nullable SDWebImageContext *)context {
id<SDImageLoader> imageLoader; id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader];
if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) { if (!imageLoader) {
imageLoader = context[SDWebImageContextImageLoader];
} else {
imageLoader = self.imageLoader; imageLoader = self.imageLoader;
} }
// Check whether we should block failed url // Check whether we should block failed url

View File

@ -60,7 +60,7 @@ typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;
operation = [operationDictionary objectForKey:key]; operation = [operationDictionary objectForKey:key];
} }
if (operation) { if (operation) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) { if ([operation respondsToSelector:@selector(cancel)]) {
[operation cancel]; [operation cancel];
} }
@synchronized (self) { @synchronized (self) {

View File

@ -84,7 +84,11 @@
NSURL *imageURL2 = [NSURL URLWithString:kTestPNGURL]; NSURL *imageURL2 = [NSURL URLWithString:kTestPNGURL];
NSURL *imageURL3 = [NSURL URLWithString:kTestGIFURL]; NSURL *imageURL3 = [NSURL URLWithString:kTestGIFURL];
// we try to set a usual NSOperation as operation class. Should not work // we try to set a usual NSOperation as operation class. Should not work
downloader.config.operationClass = [NSOperation class]; @try {
downloader.config.operationClass = [NSOperation class];
} @catch (NSException *exception) {
expect(exception).notTo.beNil();
}
SDWebImageDownloadToken *token = [downloader downloadImageWithURL:imageURL1 options:0 progress:nil completed:nil]; SDWebImageDownloadToken *token = [downloader downloadImageWithURL:imageURL1 options:0 progress:nil completed:nil];
NSOperation<SDWebImageDownloaderOperation> *operation = token.downloadOperation; NSOperation<SDWebImageDownloaderOperation> *operation = token.downloadOperation;
expect([operation class]).to.equal([SDWebImageDownloaderOperation class]); expect([operation class]).to.equal([SDWebImageDownloaderOperation class]);