From b46fe38c7257a534d02311192411e6dae43beca6 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sat, 29 Oct 2022 19:50:45 +0800 Subject: [PATCH] Quick fix the issue that UIImage.sd_decodeOptions cause retain cycle when pass custom cache in context option Now the SDImageCoderWebImageContext does not pass the built-in options, only custom options will be passed, and deprecated in 5.14.0 --- SDWebImage/Core/SDImageCacheDefine.m | 36 +++++++++++++++++++++++++++- SDWebImage/Core/SDImageCoder.h | 2 ++ Tests/Tests/SDWebImageManagerTests.m | 12 ++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/SDWebImage/Core/SDImageCacheDefine.m b/SDWebImage/Core/SDImageCacheDefine.m index 8ee8750c..f2328cdf 100644 --- a/SDWebImage/Core/SDImageCacheDefine.m +++ b/SDWebImage/Core/SDImageCacheDefine.m @@ -13,6 +13,37 @@ #import "UIImage+Metadata.h" #import "SDInternalMacros.h" +static NSArray* GetKnownContextOptions(void) { + static NSArray *knownContextOptions; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + knownContextOptions = + [NSArray arrayWithObjects: + SDWebImageContextSetImageOperationKey, + SDWebImageContextCustomManager, + SDWebImageContextImageCache, + SDWebImageContextImageLoader, + SDWebImageContextImageCoder, + SDWebImageContextImageTransformer, + SDWebImageContextImageScaleFactor, + SDWebImageContextImagePreserveAspectRatio, + SDWebImageContextImageThumbnailPixelSize, + SDWebImageContextQueryCacheType, + SDWebImageContextStoreCacheType, + SDWebImageContextOriginalQueryCacheType, + SDWebImageContextOriginalStoreCacheType, + SDWebImageContextOriginalImageCache, + SDWebImageContextAnimatedImageClass, + SDWebImageContextDownloadRequestModifier, + SDWebImageContextDownloadResponseModifier, + SDWebImageContextDownloadDecryptor, + SDWebImageContextCacheKeyFilter, + SDWebImageContextCacheSerializer + , nil]; + }); + return knownContextOptions; +} + SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext * _Nullable context, SDWebImageOptions options, NSString * _Nonnull cacheKey) { BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly); NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor]; @@ -34,7 +65,10 @@ SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext * mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale); mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue; mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue; - mutableCoderOptions[SDImageCoderWebImageContext] = context; + // Hack to remove all known context options before SDWebImage 5.14.0 + SDImageCoderMutableOptions *mutableContext = [NSMutableDictionary dictionaryWithDictionary:context]; + [mutableContext removeObjectsForKeys:GetKnownContextOptions()]; + mutableCoderOptions[SDImageCoderWebImageContext] = [mutableContext copy]; SDImageCoderOptions *coderOptions = [mutableCoderOptions copy]; return coderOptions; diff --git a/SDWebImage/Core/SDImageCoder.h b/SDWebImage/Core/SDImageCoder.h index 53b52e5d..a5535ed8 100644 --- a/SDWebImage/Core/SDImageCoder.h +++ b/SDWebImage/Core/SDImageCoder.h @@ -91,7 +91,9 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeEmbedThumb A SDWebImageContext object which hold the original context options from top-level API. (SDWebImageContext) This option is ignored for all built-in coders and take no effect. But this may be useful for some custom coders, because some business logic may dependent on things other than image or image data information only. + Only the unknown context from top-level API (See SDWebImageDefine.h) may be passed in during image loading. See `SDWebImageContext` for more detailed information. + @warning This option will be removed in 5.14.0 */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext API_DEPRECATED("The coder component will be seperated from Core subspec in the future. Update your code to not rely on this context option.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED)); diff --git a/Tests/Tests/SDWebImageManagerTests.m b/Tests/Tests/SDWebImageManagerTests.m index 41b37093..875c136e 100644 --- a/Tests/Tests/SDWebImageManagerTests.m +++ b/Tests/Tests/SDWebImageManagerTests.m @@ -538,6 +538,18 @@ [self waitForExpectationsWithTimeout:kAsyncTestTimeout * 5 handler:nil]; } +- (void)test20ThatContextPassedToLoaderDoesNotContainsBuiltIn { + XCTestExpectation *expectation = [self expectationWithDescription:@"The SDImageCoderWebImageContext should contains only unknown context to avoid retain cycle"]; + NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/502x502.png"]; + [SDWebImageManager.sharedManager loadImageWithURL:url options:0 context:@{SDWebImageContextImageScaleFactor : @(2), @"Foo": @"Bar"} progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { + SDImageCoderOptions *decodeOptions = image.sd_decodeOptions; + SDWebImageContext *retrievedContext = decodeOptions[SDImageCoderWebImageContext]; + expect(retrievedContext[@"Foo"]).equal(@"Bar"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithCommonTimeout]; +} + - (NSString *)testJPEGPath { NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; return [testBundle pathForResource:@"TestImage" ofType:@"jpg"];