diff --git a/SDWebImage/Core/UIView+WebCache.m b/SDWebImage/Core/UIView+WebCache.m index 5e9f030a..c12b43d7 100644 --- a/SDWebImage/Core/UIView+WebCache.m +++ b/SDWebImage/Core/UIView+WebCache.m @@ -52,10 +52,19 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; setImageBlock:(nullable SDSetImageBlock)setImageBlock progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { - context = [context copy]; // copy to avoid mutable object + if (context) { + // copy to avoid mutable object + context = [context copy]; + } else { + context = [NSDictionary dictionary]; + } NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey]; if (!validOperationKey) { + // pass through the operation key to downstream, which can used for tracing operation or image view class validOperationKey = NSStringFromClass([self class]); + SDWebImageMutableContext *mutableContext = [context mutableCopy]; + mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey; + context = [mutableContext copy]; } self.sd_latestOperationKey = validOperationKey; [self sd_cancelImageLoadOperationWithKey:validOperationKey]; @@ -83,6 +92,11 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; SDWebImageManager *manager = context[SDWebImageContextCustomManager]; if (!manager) { manager = [SDWebImageManager sharedManager]; + } else { + // remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager) + SDWebImageMutableContext *mutableContext = [context mutableCopy]; + mutableContext[SDWebImageContextCustomManager] = nil; + context = [mutableContext copy]; } SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { diff --git a/Tests/Tests/SDWebCacheCategoriesTests.m b/Tests/Tests/SDWebCacheCategoriesTests.m index e91c5ece..96fc7a3e 100644 --- a/Tests/Tests/SDWebCacheCategoriesTests.m +++ b/Tests/Tests/SDWebCacheCategoriesTests.m @@ -350,6 +350,32 @@ [self waitForExpectationsWithCommonTimeout]; } +- (void)testUIViewOperationKeyContextWorks { + XCTestExpectation *expectation = [self expectationWithDescription:@"UIView operation key context should pass through"]; + + UIView *view = [[UIView alloc] init]; + NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL]; + SDWebImageManager *customManager = [[SDWebImageManager alloc] initWithCache:SDImageCachesManager.sharedManager loader:SDImageLoadersManager.sharedManager]; + customManager.optionsProcessor = [SDWebImageOptionsProcessor optionsProcessorWithBlock:^SDWebImageOptionsResult * _Nullable(NSURL * _Nullable url, SDWebImageOptions options, SDWebImageContext * _Nullable context) { + // expect manager does not exist, avoid retain cycle + expect(context[SDWebImageContextCustomManager]).beNil(); + // expect operation key to be the image view class + expect(context[SDWebImageContextSetImageOperationKey]).equal(NSStringFromClass(view.class)); + return [[SDWebImageOptionsResult alloc] initWithOptions:options context:context]; + }]; + [view sd_internalSetImageWithURL:originalImageURL + placeholderImage:nil + options:0 + context:@{SDWebImageContextCustomManager: customManager} + setImageBlock:nil + progress:nil + completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { + [expectation fulfill]; + }]; + + [self waitForExpectationsWithCommonTimeout]; +} + #pragma mark - Helper - (UIWindow *)window { if (!_window) {