diff --git a/SDWebImage/Core/SDAnimatedImageView.m b/SDWebImage/Core/SDAnimatedImageView.m index 35260315..db7d7675 100644 --- a/SDWebImage/Core/SDAnimatedImageView.m +++ b/SDWebImage/Core/SDAnimatedImageView.m @@ -471,7 +471,7 @@ { if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)] && image.sd_isIncremental && [image respondsToSelector:@selector(animatedCoder)]) { id animatedCoder = [(id)image animatedCoder]; - if ([animatedCoder conformsToProtocol:@protocol(SDProgressiveImageCoder)]) { + if ([animatedCoder respondsToSelector:@selector(initIncrementalWithOptions:)]) { return (id)animatedCoder; } } diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m index 3dbae354..5810996c 100644 --- a/SDWebImage/Core/SDImageCache.m +++ b/SDWebImage/Core/SDImageCache.m @@ -245,7 +245,7 @@ static NSString * _defaultDiskCacheDirectory; dispatch_async(self.ioQueue, ^{ @autoreleasepool { 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 data = [((id)image) animatedImageData]; } @@ -440,8 +440,7 @@ static NSString * _defaultDiskCacheDirectory; if (image) { if (options & SDImageCacheDecodeFirstFrameOnly) { // Ensure static image - Class animatedImageClass = image.class; - if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) { + if (image.sd_isAnimated) { #if SD_MAC image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp]; #else @@ -574,8 +573,7 @@ static NSString * _defaultDiskCacheDirectory; if (image) { if (options & SDImageCacheDecodeFirstFrameOnly) { // Ensure static image - Class animatedImageClass = image.class; - if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) { + if (image.sd_isAnimated) { #if SD_MAC image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp]; #else diff --git a/SDWebImage/Core/SDImageCacheDefine.m b/SDWebImage/Core/SDImageCacheDefine.m index 0e9ec44b..8322d76d 100644 --- a/SDWebImage/Core/SDImageCacheDefine.m +++ b/SDWebImage/Core/SDImageCacheDefine.m @@ -94,10 +94,8 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue]; // Grab the image coder - id imageCoder; - if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) { - imageCoder = context[SDWebImageContextImageCoder]; - } else { + id imageCoder = context[SDWebImageContextImageCoder]; + if (!imageCoder) { imageCoder = [SDImageCodersManager sharedManager]; } @@ -128,12 +126,6 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS if (lazyDecode) { // lazyDecode = NO means we should not forceDecode, highest priority 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) { image = [SDImageCoderHelper decodedImageWithImage:image]; diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m index deaf0236..893eb350 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.m +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m @@ -23,8 +23,8 @@ static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizati // Specify File Size for lossy format encoding, like JPEG static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize"; -// Only assert on Debug mode and Simulator -#define SD_CHECK_CGIMAGE_RETAIN_SOURCE DEBUG && TARGET_OS_SIMULATOR && \ +// Only assert on Debug mode +#define SD_CHECK_CGIMAGE_RETAIN_SOURCE DEBUG && \ ((__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_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 - // Assert here to check CGImageRef should not retain the CGImageSourceRef and has possible thread-safe issue (this is behavior on iOS 15+) - // If assert hit, fire issue to https://github.com/SDWebImage/SDWebImage/issues and we update the condition for this behavior check - extern CGImageSourceRef CGImageGetImageSource(CGImageRef); - NSCAssert(!CGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock"); + if (@available(iOS 15, tvOS 15, *)) { + // Assert here to check CGImageRef should not retain the CGImageSourceRef and has possible thread-safe issue (this is behavior on iOS 15+) + // If assert hit, fire issue to https://github.com/SDWebImage/SDWebImage/issues and we update the condition for this behavior check + extern CGImageSourceRef CGImageGetImageSource(CGImageRef); + NSCAssert(!CGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock"); + } #endif #if SD_UIKIT || SD_WATCH diff --git a/SDWebImage/Core/SDImageLoader.m b/SDWebImage/Core/SDImageLoader.m index c8148d8b..dc547918 100644 --- a/SDWebImage/Core/SDImageLoader.m +++ b/SDWebImage/Core/SDImageLoader.m @@ -47,10 +47,8 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue]; // Grab the image coder - id imageCoder; - if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) { - imageCoder = context[SDWebImageContextImageCoder]; - } else { + id imageCoder = context[SDWebImageContextImageCoder]; + if (!imageCoder) { imageCoder = [SDImageCodersManager sharedManager]; } @@ -81,12 +79,6 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS if (lazyDecode) { // lazyDecode = NO means we should not forceDecode, highest priority 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) { image = [SDImageCoderHelper decodedImageWithImage:image]; @@ -120,7 +112,7 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im if (!progressiveCoder) { id imageCoder = context[SDWebImageContextImageCoder]; // Check the progressive coder if provided - if ([imageCoder conformsToProtocol:@protocol(SDProgressiveImageCoder)]) { + if ([imageCoder respondsToSelector:@selector(initIncrementalWithOptions:)]) { progressiveCoder = [[[imageCoder class] alloc] initIncrementalWithOptions:coderOptions]; } else { // 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) { // check whether we should use `SDAnimatedImage` 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)progressiveCoder scale:scale]; if (image) { // Progressive decoding does not preload frames @@ -164,12 +156,6 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im if (lazyDecode) { // lazyDecode = NO means we should not forceDecode, highest priority 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) { image = [SDImageCoderHelper decodedImageWithImage:image]; diff --git a/SDWebImage/Core/SDWebImageDownloader.m b/SDWebImage/Core/SDWebImageDownloader.m index fbd8a5bd..8e66a8ca 100644 --- a/SDWebImage/Core/SDWebImageDownloader.m +++ b/SDWebImage/Core/SDWebImageDownloader.m @@ -353,9 +353,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; // Operation Class Class operationClass = self.config.operationClass; - if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) { - // Custom operation class - } else { + if (!operationClass) { operationClass = [SDWebImageDownloaderOperation class]; } NSOperation *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context]; diff --git a/SDWebImage/Core/SDWebImageDownloaderConfig.m b/SDWebImage/Core/SDWebImageDownloaderConfig.m index 6120bd8a..6738b341 100644 --- a/SDWebImage/Core/SDWebImageDownloaderConfig.m +++ b/SDWebImage/Core/SDWebImageDownloaderConfig.m @@ -7,6 +7,7 @@ */ #import "SDWebImageDownloaderConfig.h" +#import "SDWebImageDownloaderOperation.h" static SDWebImageDownloaderConfig * _defaultDownloaderConfig; @@ -48,5 +49,12 @@ static SDWebImageDownloaderConfig * _defaultDownloaderConfig; 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 diff --git a/SDWebImage/Core/SDWebImageManager.m b/SDWebImage/Core/SDWebImageManager.m index 6bb7c147..a0f1f118 100644 --- a/SDWebImage/Core/SDWebImageManager.m +++ b/SDWebImage/Core/SDWebImageManager.m @@ -170,7 +170,7 @@ static id _defaultImageLoader; id transformer = self.transformer; if (context[SDWebImageContextImageTransformer]) { transformer = context[SDWebImageContextImageTransformer]; - if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) { + if ([transformer isEqual:NSNull.null]) { transformer = nil; } } @@ -286,10 +286,8 @@ static id _defaultImageLoader; progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { // Grab the image cache to use - id imageCache; - if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { - imageCache = context[SDWebImageContextImageCache]; - } else { + id imageCache = context[SDWebImageContextImageCache]; + if (!imageCache) { imageCache = self.imageCache; } // Get the query cache type @@ -338,14 +336,11 @@ static id _defaultImageLoader; progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { // Grab the image cache to use, choose standalone original cache firstly - id imageCache; - if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) { - imageCache = context[SDWebImageContextOriginalImageCache]; - } else { + id imageCache = context[SDWebImageContextOriginalImageCache]; + if (!imageCache) { // if no standalone cache available, use default cache - if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { - imageCache = context[SDWebImageContextImageCache]; - } else { + imageCache = context[SDWebImageContextImageCache]; + if (!imageCache) { imageCache = self.imageCache; } } @@ -401,10 +396,8 @@ static id _defaultImageLoader; } // Grab the image loader to use - id imageLoader; - if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) { - imageLoader = context[SDWebImageContextImageLoader]; - } else { + id imageLoader = context[SDWebImageContextImageLoader]; + if (!imageLoader) { imageLoader = self.imageLoader; } @@ -488,7 +481,7 @@ static id _defaultImageLoader; finished:(BOOL)finished completed:(nullable SDInternalCompletionBlock)completedBlock { id transformer = context[SDWebImageContextImageTransformer]; - if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) { + if ([transformer isEqual:NSNull.null]) { transformer = nil; } // transformer check @@ -546,14 +539,11 @@ static id _defaultImageLoader; finished:(BOOL)finished completed:(nullable SDInternalCompletionBlock)completedBlock { // Grab the image cache to use, choose standalone original cache firstly - id imageCache; - if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) { - imageCache = context[SDWebImageContextOriginalImageCache]; - } else { + id imageCache = context[SDWebImageContextOriginalImageCache]; + if (!imageCache) { // if no standalone cache available, use default cache - if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { - imageCache = context[SDWebImageContextImageCache]; - } else { + imageCache = context[SDWebImageContextImageCache]; + if (!imageCache) { imageCache = self.imageCache; } } @@ -605,10 +595,8 @@ static id _defaultImageLoader; finished:(BOOL)finished completed:(nullable SDInternalCompletionBlock)completedBlock { // Grab the image cache to use - id imageCache; - if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) { - imageCache = context[SDWebImageContextImageCache]; - } else { + id imageCache = context[SDWebImageContextImageCache]; + if (!imageCache) { imageCache = self.imageCache; } BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache); @@ -704,10 +692,8 @@ static id _defaultImageLoader; error:(nonnull NSError *)error options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context { - id imageLoader; - if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) { - imageLoader = context[SDWebImageContextImageLoader]; - } else { + id imageLoader = context[SDWebImageContextImageLoader]; + if (!imageLoader) { imageLoader = self.imageLoader; } // Check whether we should block failed url diff --git a/SDWebImage/Core/UIView+WebCacheOperation.m b/SDWebImage/Core/UIView+WebCacheOperation.m index 6fe23e48..e884c4cf 100644 --- a/SDWebImage/Core/UIView+WebCacheOperation.m +++ b/SDWebImage/Core/UIView+WebCacheOperation.m @@ -60,7 +60,7 @@ typedef NSMapTable> SDOperationsDictionary; operation = [operationDictionary objectForKey:key]; } if (operation) { - if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) { + if ([operation respondsToSelector:@selector(cancel)]) { [operation cancel]; } @synchronized (self) { diff --git a/Tests/Tests/SDWebImageDownloaderTests.m b/Tests/Tests/SDWebImageDownloaderTests.m index 7639157b..b6cfb6fa 100644 --- a/Tests/Tests/SDWebImageDownloaderTests.m +++ b/Tests/Tests/SDWebImageDownloaderTests.m @@ -84,7 +84,11 @@ NSURL *imageURL2 = [NSURL URLWithString:kTestPNGURL]; NSURL *imageURL3 = [NSURL URLWithString:kTestGIFURL]; // 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]; NSOperation *operation = token.downloadOperation; expect([operation class]).to.equal([SDWebImageDownloaderOperation class]);