diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index 8283cc4a..83ed41bc 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -22,6 +22,10 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 320797442A76287D00B17CF5 /* UIView+WebCacheState.h in Headers */ = {isa = PBXBuildFile; fileRef = 320797422A76287D00B17CF5 /* UIView+WebCacheState.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 320797452A76287D00B17CF5 /* UIView+WebCacheState.m in Sources */ = {isa = PBXBuildFile; fileRef = 320797432A76287D00B17CF5 /* UIView+WebCacheState.m */; }; + 320797472A76288C00B17CF5 /* UIView+WebCacheState.m in Sources */ = {isa = PBXBuildFile; fileRef = 320797432A76287D00B17CF5 /* UIView+WebCacheState.m */; }; + 3207974C2A7628CB00B17CF5 /* UIView+WebCacheState.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 320797422A76287D00B17CF5 /* UIView+WebCacheState.h */; }; 320CAE172086F50500CFFC80 /* SDWebImageError.h in Headers */ = {isa = PBXBuildFile; fileRef = 320CAE132086F50500CFFC80 /* SDWebImageError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 320CAE1B2086F50500CFFC80 /* SDWebImageError.m in Sources */ = {isa = PBXBuildFile; fileRef = 320CAE142086F50500CFFC80 /* SDWebImageError.m */; }; 320CAE1D2086F50500CFFC80 /* SDWebImageError.m in Sources */ = {isa = PBXBuildFile; fileRef = 320CAE142086F50500CFFC80 /* SDWebImageError.m */; }; @@ -323,6 +327,7 @@ dstPath = include/SDWebImage; dstSubfolderSpec = 16; files = ( + 3207974C2A7628CB00B17CF5 /* UIView+WebCacheState.h in Copy Headers */, 325074F2296C546D00B730CF /* SDCallbackQueue.h in Copy Headers */, 32D9EE4B24AF259B00EAFDF4 /* SDImageAWebPCoder.h in Copy Headers */, 328E9DE523A61DD30051C893 /* SDGraphicsImageRenderer.h in Copy Headers */, @@ -392,6 +397,8 @@ /* Begin PBXFileReference section */ 320224B9203979BA00E9F285 /* SDAnimatedImageRep.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImageRep.h; path = Core/SDAnimatedImageRep.h; sourceTree = ""; }; 320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageRep.m; path = Core/SDAnimatedImageRep.m; sourceTree = ""; }; + 320797422A76287D00B17CF5 /* UIView+WebCacheState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView+WebCacheState.h"; path = "Core/UIView+WebCacheState.h"; sourceTree = ""; }; + 320797432A76287D00B17CF5 /* UIView+WebCacheState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIView+WebCacheState.m"; path = "Core/UIView+WebCacheState.m"; sourceTree = ""; }; 320CAE132086F50500CFFC80 /* SDWebImageError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageError.h; path = Core/SDWebImageError.h; sourceTree = ""; }; 320CAE142086F50500CFFC80 /* SDWebImageError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageError.m; path = Core/SDWebImageError.m; sourceTree = ""; }; 321117A7296573680001FC2C /* SDCallbackQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDCallbackQueue.h; path = Core/SDCallbackQueue.h; sourceTree = ""; }; @@ -824,6 +831,8 @@ 4397D2F51D0DE2DF00BB2784 /* NSImage+Compatibility.m */, AB615301192DA24600A2D8E9 /* UIView+WebCacheOperation.h */, AB615302192DA24600A2D8E9 /* UIView+WebCacheOperation.m */, + 320797422A76287D00B17CF5 /* UIView+WebCacheState.h */, + 320797432A76287D00B17CF5 /* UIView+WebCacheState.m */, ); name = Categories; sourceTree = ""; @@ -979,6 +988,7 @@ 32C0FDE32013426C001B8F2D /* SDWebImageIndicator.h in Headers */, 32F7C0712030114C00873181 /* SDImageTransformer.h in Headers */, 32E67311235765B500DB4987 /* SDDisplayLink.h in Headers */, + 320797442A76287D00B17CF5 /* UIView+WebCacheState.h in Headers */, 325F7CC623893B2E00AEDFCC /* SDFileAttributeHelper.h in Headers */, 4A2CAE2D1AB4BB7500B6BC39 /* UIImage+GIF.h in Headers */, 4A2CAE291AB4BB7500B6BC39 /* NSData+ImageContentType.h in Headers */, @@ -1209,6 +1219,7 @@ 325F7CC723893B2E00AEDFCC /* SDFileAttributeHelper.m in Sources */, 3248475F201775F600AF9E5A /* SDAnimatedImageView.m in Sources */, 32D1222C2080B2EB003685A3 /* SDImageCachesManager.m in Sources */, + 320797452A76287D00B17CF5 /* UIView+WebCacheState.m in Sources */, 32B9B53F206ED4230026769D /* SDWebImageDownloaderConfig.m in Sources */, 43A9186D1D8308FE00B3925F /* SDImageCacheConfig.m in Sources */, 32A09E42233358B700339F9D /* SDImageIOAnimatedCoder.m in Sources */, @@ -1269,6 +1280,7 @@ 32F7C0752030114C00873181 /* SDImageTransformer.m in Sources */, 3237F9EB20161AE000A88143 /* NSImage+Compatibility.m in Sources */, 32C0FDE72013426C001B8F2D /* SDWebImageIndicator.m in Sources */, + 320797472A76288C00B17CF5 /* UIView+WebCacheState.m in Sources */, 32B5CC63222F8B70005EB74E /* SDAsyncBlockOperation.m in Sources */, 32F21B5720788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m in Sources */, 3237321629F8D0E200D1DA41 /* SDImageFramePool.m in Sources */, diff --git a/SDWebImage/Core/NSButton+WebCache.m b/SDWebImage/Core/NSButton+WebCache.m index 0c083e56..953baf11 100644 --- a/SDWebImage/Core/NSButton+WebCache.m +++ b/SDWebImage/Core/NSButton+WebCache.m @@ -12,11 +12,10 @@ #import "objc/runtime.h" #import "UIView+WebCacheOperation.h" +#import "UIView+WebCacheState.h" #import "UIView+WebCache.h" #import "SDInternalMacros.h" -static NSString * const SDAlternateImageOperationKey = @"NSButtonAlternateImageOperation"; - @implementation NSButton (WebCache) #pragma mark - Image @@ -59,7 +58,6 @@ static NSString * const SDAlternateImageOperationKey = @"NSButtonAlternateImageO context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { - self.sd_currentImageURL = url; [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options @@ -113,15 +111,13 @@ static NSString * const SDAlternateImageOperationKey = @"NSButtonAlternateImageO context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { - self.sd_currentAlternateImageURL = url; - SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } - mutableContext[SDWebImageContextSetImageOperationKey] = SDAlternateImageOperationKey; + mutableContext[SDWebImageContextSetImageOperationKey] = @keypath(self, alternateImage); @weakify(self); [self sd_internalSetImageWithURL:url placeholderImage:placeholder @@ -142,29 +138,23 @@ static NSString * const SDAlternateImageOperationKey = @"NSButtonAlternateImageO #pragma mark - Cancel - (void)sd_cancelCurrentImageLoad { - [self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])]; + [self sd_cancelImageLoadOperationWithKey:nil]; } - (void)sd_cancelCurrentAlternateImageLoad { - [self sd_cancelImageLoadOperationWithKey:SDAlternateImageOperationKey]; + [self sd_cancelImageLoadOperationWithKey:@keypath(self, alternateImage)]; } -#pragma mark - Private +#pragma mark - State - (NSURL *)sd_currentImageURL { - return objc_getAssociatedObject(self, @selector(sd_currentImageURL)); + return [self sd_imageLoadStateForKey:nil].url; } -- (void)setSd_currentImageURL:(NSURL *)sd_currentImageURL { - objc_setAssociatedObject(self, @selector(sd_currentImageURL), sd_currentImageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} +#pragma mark - Alternate State - (NSURL *)sd_currentAlternateImageURL { - return objc_getAssociatedObject(self, @selector(sd_currentAlternateImageURL)); -} - -- (void)setSd_currentAlternateImageURL:(NSURL *)sd_currentAlternateImageURL { - objc_setAssociatedObject(self, @selector(sd_currentAlternateImageURL), sd_currentAlternateImageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return [self sd_imageLoadStateForKey:@keypath(self, alternateImage)].url; } @end diff --git a/SDWebImage/Core/UIButton+WebCache.h b/SDWebImage/Core/UIButton+WebCache.h index 89d94b46..4b637254 100644 --- a/SDWebImage/Core/UIButton+WebCache.h +++ b/SDWebImage/Core/UIButton+WebCache.h @@ -21,6 +21,7 @@ /** * Get the current image URL. + * This simply translate to `[self sd_imageURLForState:self.state]` from v5.17.0 */ @property (nonatomic, strong, readonly, nullable) NSURL *sd_currentImageURL; @@ -31,6 +32,13 @@ */ - (nullable NSURL *)sd_imageURLForState:(UIControlState)state; +/** + * Get the image operation key for a control state. + * + * @param state Which state you want to know the URL for. The values are described in UIControlState. + */ +- (nonnull NSString *)sd_imageOperationKeyForState:(UIControlState)state; + /** * Set the button `image` with an `url`. * @@ -202,6 +210,13 @@ */ @property (nonatomic, strong, readonly, nullable) NSURL *sd_currentBackgroundImageURL; +/** + * Get the background image operation key for a control state. + * + * @param state Which state you want to know the URL for. The values are described in UIControlState. + */ +- (nonnull NSString *)sd_backgroundImageOperationKeyForState:(UIControlState)state; + /** * Get the background image URL for a control state. * diff --git a/SDWebImage/Core/UIButton+WebCache.m b/SDWebImage/Core/UIButton+WebCache.m index 4ccd0291..a6814369 100644 --- a/SDWebImage/Core/UIButton+WebCache.m +++ b/SDWebImage/Core/UIButton+WebCache.m @@ -12,47 +12,14 @@ #import "objc/runtime.h" #import "UIView+WebCacheOperation.h" +#import "UIView+WebCacheState.h" #import "UIView+WebCache.h" #import "SDInternalMacros.h" -static char imageURLStorageKey; - -typedef NSMutableDictionary SDStateImageURLDictionary; - -static inline NSString * imageURLKeyForState(UIControlState state) { - return [NSString stringWithFormat:@"image_%lu", (unsigned long)state]; -} - -static inline NSString * backgroundImageURLKeyForState(UIControlState state) { - return [NSString stringWithFormat:@"backgroundImage_%lu", (unsigned long)state]; -} - -static inline NSString * imageOperationKeyForState(UIControlState state) { - return [NSString stringWithFormat:@"UIButtonImageOperation%lu", (unsigned long)state]; -} - -static inline NSString * backgroundImageOperationKeyForState(UIControlState state) { - return [NSString stringWithFormat:@"UIButtonBackgroundImageOperation%lu", (unsigned long)state]; -} - @implementation UIButton (WebCache) #pragma mark - Image -- (nullable NSURL *)sd_currentImageURL { - NSURL *url = self.sd_imageURLStorage[imageURLKeyForState(self.state)]; - - if (!url) { - url = self.sd_imageURLStorage[imageURLKeyForState(UIControlStateNormal)]; - } - - return url; -} - -- (nullable NSURL *)sd_imageURLForState:(UIControlState)state { - return self.sd_imageURLStorage[imageURLKeyForState(state)]; -} - - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state { [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil]; } @@ -92,19 +59,13 @@ static inline NSString * backgroundImageOperationKeyForState(UIControlState stat context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { - if (!url) { - [self.sd_imageURLStorage removeObjectForKey:imageURLKeyForState(state)]; - } else { - self.sd_imageURLStorage[imageURLKeyForState(state)] = url; - } - SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } - mutableContext[SDWebImageContextSetImageOperationKey] = imageOperationKeyForState(state); + mutableContext[SDWebImageContextSetImageOperationKey] = [self sd_imageOperationKeyForState:state]; @weakify(self); [self sd_internalSetImageWithURL:url placeholderImage:placeholder @@ -124,20 +85,6 @@ static inline NSString * backgroundImageOperationKeyForState(UIControlState stat #pragma mark - Background Image -- (nullable NSURL *)sd_currentBackgroundImageURL { - NSURL *url = self.sd_imageURLStorage[backgroundImageURLKeyForState(self.state)]; - - if (!url) { - url = self.sd_imageURLStorage[backgroundImageURLKeyForState(UIControlStateNormal)]; - } - - return url; -} - -- (nullable NSURL *)sd_backgroundImageURLForState:(UIControlState)state { - return self.sd_imageURLStorage[backgroundImageURLKeyForState(state)]; -} - - (void)sd_setBackgroundImageWithURL:(nullable NSURL *)url forState:(UIControlState)state { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil]; } @@ -177,19 +124,13 @@ static inline NSString * backgroundImageOperationKeyForState(UIControlState stat context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { - if (!url) { - [self.sd_imageURLStorage removeObjectForKey:backgroundImageURLKeyForState(state)]; - } else { - self.sd_imageURLStorage[backgroundImageURLKeyForState(state)] = url; - } - SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } - mutableContext[SDWebImageContextSetImageOperationKey] = backgroundImageOperationKeyForState(state); + mutableContext[SDWebImageContextSetImageOperationKey] = [self sd_backgroundImageOperationKeyForState:state]; @weakify(self); [self sd_internalSetImageWithURL:url placeholderImage:placeholder @@ -210,23 +151,46 @@ static inline NSString * backgroundImageOperationKeyForState(UIControlState stat #pragma mark - Cancel - (void)sd_cancelImageLoadForState:(UIControlState)state { - [self sd_cancelImageLoadOperationWithKey:imageOperationKeyForState(state)]; + [self sd_cancelImageLoadOperationWithKey:[self sd_imageOperationKeyForState:state]]; } - (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state { - [self sd_cancelImageLoadOperationWithKey:backgroundImageOperationKeyForState(state)]; + [self sd_cancelImageLoadOperationWithKey:[self sd_backgroundImageOperationKeyForState:state]]; } -#pragma mark - Private +#pragma mark - State -- (SDStateImageURLDictionary *)sd_imageURLStorage { - SDStateImageURLDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey); - if (!storage) { - storage = [NSMutableDictionary dictionary]; - objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +- (NSString *)sd_imageOperationKeyForState:(UIControlState)state { + return [NSString stringWithFormat:@"UIButtonImageOperation%lu", (unsigned long)state]; +} + +- (NSString *)sd_backgroundImageOperationKeyForState:(UIControlState)state { + return [NSString stringWithFormat:@"UIButtonBackgroundImageOperation%lu", (unsigned long)state]; +} + +- (NSURL *)sd_currentImageURL { + NSURL *url = [self sd_imageURLForState:self.state]; + if (!url) { + [self sd_imageURLForState:UIControlStateNormal]; } + return url; +} - return storage; +- (NSURL *)sd_imageURLForState:(UIControlState)state { + return [self sd_imageLoadStateForKey:[self sd_imageOperationKeyForState:state]].url; +} +#pragma mark - Background State + +- (NSURL *)sd_currentBackgroundImageURL { + NSURL *url = [self sd_backgroundImageURLForState:self.state]; + if (!url) { + url = [self sd_backgroundImageURLForState:UIControlStateNormal]; + } + return url; +} + +- (NSURL *)sd_backgroundImageURLForState:(UIControlState)state { + return [self sd_imageLoadStateForKey:[self sd_backgroundImageOperationKeyForState:state]].url; } @end diff --git a/SDWebImage/Core/UIImageView+HighlightedWebCache.h b/SDWebImage/Core/UIImageView+HighlightedWebCache.h index 6cd3ba61..80fabc6d 100644 --- a/SDWebImage/Core/UIImageView+HighlightedWebCache.h +++ b/SDWebImage/Core/UIImageView+HighlightedWebCache.h @@ -17,6 +17,13 @@ */ @interface UIImageView (HighlightedWebCache) +#pragma mark - Highlighted Image + +/** + * Get the current highlighted image URL. + */ +@property (nonatomic, strong, readonly, nullable) NSURL *sd_currentHighlightedImageURL; + /** * Set the imageView `highlightedImage` with an `url`. * @@ -124,6 +131,12 @@ progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; +/** + * Cancel the current highlighted image load (for `UIImageView.highlighted`) + * @note For normal image, use `sd_cancelCurrentImageLoad` + */ +- (void)sd_cancelCurrentHighlightedImageLoad; + @end #endif diff --git a/SDWebImage/Core/UIImageView+HighlightedWebCache.m b/SDWebImage/Core/UIImageView+HighlightedWebCache.m index 96c09c1c..bb51c908 100644 --- a/SDWebImage/Core/UIImageView+HighlightedWebCache.m +++ b/SDWebImage/Core/UIImageView+HighlightedWebCache.m @@ -11,11 +11,10 @@ #if SD_UIKIT #import "UIView+WebCacheOperation.h" +#import "UIView+WebCacheState.h" #import "UIView+WebCache.h" #import "SDInternalMacros.h" -static NSString * const SDHighlightedImageOperationKey = @"UIImageViewImageOperationHighlighted"; - @implementation UIImageView (HighlightedWebCache) - (void)sd_setHighlightedImageWithURL:(nullable NSURL *)url { @@ -54,7 +53,7 @@ static NSString * const SDHighlightedImageOperationKey = @"UIImageViewImageOpera } else { mutableContext = [NSMutableDictionary dictionary]; } - mutableContext[SDWebImageContextSetImageOperationKey] = SDHighlightedImageOperationKey; + mutableContext[SDWebImageContextSetImageOperationKey] = @keypath(self, highlightedImage); [self sd_internalSetImageWithURL:url placeholderImage:nil options:options @@ -71,6 +70,16 @@ static NSString * const SDHighlightedImageOperationKey = @"UIImageViewImageOpera }]; } +#pragma mark - Highlighted State + +- (NSURL *)sd_currentHighlightedImageURL { + return [self sd_imageLoadStateForKey:@keypath(self, highlightedImage)].url; +} + +- (void)sd_cancelCurrentHighlightedImageLoad { + return [self sd_cancelImageLoadOperationWithKey:@keypath(self, highlightedImage)]; +} + @end #endif diff --git a/SDWebImage/Core/UIImageView+WebCache.h b/SDWebImage/Core/UIImageView+WebCache.h index 626de9d1..46b5a707 100644 --- a/SDWebImage/Core/UIImageView+WebCache.h +++ b/SDWebImage/Core/UIImageView+WebCache.h @@ -45,6 +45,15 @@ */ @interface UIImageView (WebCache) +#pragma mark - Image State + +/** + * Get the current image URL. + */ +@property (nonatomic, strong, readonly, nullable) NSURL *sd_currentImageURL; + +#pragma mark - Image Loading + /** * Set the imageView `image` with an `url`. * @@ -191,4 +200,10 @@ progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock; +/** + * Cancel the current normal image load (for `UIImageView.image`) + * @note For highlighted image, use `sd_cancelCurrentHighlightedImageLoad` + */ +- (void)sd_cancelCurrentImageLoad; + @end diff --git a/SDWebImage/Core/UIImageView+WebCache.m b/SDWebImage/Core/UIImageView+WebCache.m index 9d7f18e7..e461aeee 100644 --- a/SDWebImage/Core/UIImageView+WebCache.m +++ b/SDWebImage/Core/UIImageView+WebCache.m @@ -9,6 +9,7 @@ #import "UIImageView+WebCache.h" #import "objc/runtime.h" #import "UIView+WebCacheOperation.h" +#import "UIView+WebCacheState.h" #import "UIView+WebCache.h" @implementation UIImageView (WebCache) @@ -64,4 +65,14 @@ }]; } +#pragma mark - State + +- (NSURL *)sd_currentImageURL { + return [self sd_imageLoadStateForKey:nil].url; +} + +- (void)sd_cancelCurrentImageLoad { + return [self sd_cancelImageLoadOperationWithKey:nil]; +} + @end diff --git a/SDWebImage/Core/UIView+WebCache.h b/SDWebImage/Core/UIView+WebCache.h index 48175f17..b0f271c0 100644 --- a/SDWebImage/Core/UIView+WebCache.h +++ b/SDWebImage/Core/UIView+WebCache.h @@ -11,6 +11,8 @@ #import "SDWebImageManager.h" #import "SDWebImageTransition.h" #import "SDWebImageIndicator.h" +#import "UIView+WebCacheOperation.h" +#import "UIView+WebCacheState.h" /** The value specify that the image progress unit count cannot be determined because the progressBlock is not been called. @@ -24,27 +26,34 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima */ @interface UIView (WebCache) -/** - * Get the current image URL. - * - * @note Note that because of the limitations of categories this property can get out of sync if you use setImage: directly. - */ -@property (nonatomic, strong, readonly, nullable) NSURL *sd_imageURL; - /** * Get the current image operation key. Operation key is used to identify the different queries for one view instance (like UIButton). * See more about this in `SDWebImageContextSetImageOperationKey`. - * If you cancel current image load, the key will be set to nil. + * * @note You can use method `UIView+WebCacheOperation` to investigate different queries' operation. + * @note For the history version compatible, when current UIView has property exactly called `image`, the operation key will use `NSStringFromClass(self.class)`. Include `UIImageView.image/NSImageView.image/NSButton.image` (without `UIButton`) + * @warning This property should be only used for single state view, like `UIImageView` without highlighted state. For stateful view like `UIBUtton` (one view can have multiple images loading), check their header to call correct API, like `UIButton sd_imageURLForState:` */ @property (nonatomic, strong, readonly, nullable) NSString *sd_latestOperationKey; +#pragma mark - State + +/** + * Get the current image URL. + * This simply translate to `[self sd_imageLoadStateForKey:self.sd_latestOperationKey].url` from v5.17.0 + * + * @note Note that because of the limitations of categories this property can get out of sync if you use setImage: directly. + * @warning This property should be only used for single state view, like `UIImageView` without highlighted state. For stateful view like `UIBUtton` (one view can have multiple images loading), use `sd_imageLoadStateForKey:` instead. See `UIView+WebCacheState.h` for more information. + */ +@property (nonatomic, strong, readonly, nullable) NSURL *sd_imageURL; + /** * The current image loading progress associated to the view. The unit count is the received size and excepted size of download. * The `totalUnitCount` and `completedUnitCount` will be reset to 0 after a new image loading start (change from current queue). And they will be set to `SDWebImageProgressUnitCountUnknown` if the progressBlock not been called but the image loading success to mark the progress finished (change from main queue). * @note You can use Key-Value Observing on the progress, but you should take care that the change to progress is from a background queue during download(the same as progressBlock). If you want to using KVO and update the UI, make sure to dispatch on the main queue. And it's recommend to use some KVO libs like KVOController because it's more safe and easy to use. * @note The getter will create a progress instance if the value is nil. But by default, we don't create one. If you need to use Key-Value Observing, you must trigger the getter or set a custom progress instance before the loading start. The default value is nil. * @note Note that because of the limitations of categories this property can get out of sync if you update the progress directly. + * @warning This property should be only used for single state view, like `UIImageView` without highlighted state. For stateful view like `UIBUtton` (one view can have multiple images loading), use `sd_imageLoadStateForKey:` instead. See `UIView+WebCacheState.h` for more information. */ @property (nonatomic, strong, null_resettable) NSProgress *sd_imageProgress; @@ -83,6 +92,9 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima /** * Cancel the current image load + * This simply translate to `[self sd_cancelImageLoadOperationWithKey:self.sd_latestOperationKey]` from v5.17.0 + * + * @warning This method should be only used for single state view, like `UIImageView` without highlighted state. For stateful view like `UIBUtton` (one view can have multiple images loading), use `sd_cancelImageLoadOperationWithKey:` instead. See `UIView+WebCacheOperation.h` for more information. */ - (void)sd_cancelCurrentImageLoad; @@ -93,6 +105,7 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima /** The image transition when image load finished. See `SDWebImageTransition`. If you specify nil, do not do transition. Defaults to nil. + @warning This property should be only used for single state view, like `UIImageView` without highlighted state. For stateful view like `UIBUtton` (one view can have multiple images loading), write your own implementation in `setImageBlock:`, and check current stateful view's state to render the UI. */ @property (nonatomic, strong, nullable) SDWebImageTransition *sd_imageTransition; @@ -102,6 +115,7 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima The image indicator during the image loading. If you do not need indicator, specify nil. Defaults to nil The setter will remove the old indicator view and add new indicator view to current view's subview. @note Because this is UI related, you should access only from the main queue. + @warning This property should be only used for single state view, like `UIImageView` without highlighted state. For stateful view like `UIBUtton` (one view can have multiple images loading), write your own implementation in `setImageBlock:`, and check current stateful view's state to render the UI. */ @property (nonatomic, strong, nullable) id sd_imageIndicator; diff --git a/SDWebImage/Core/UIView+WebCache.m b/SDWebImage/Core/UIView+WebCache.m index 569d53f2..2f671c00 100644 --- a/SDWebImage/Core/UIView+WebCache.m +++ b/SDWebImage/Core/UIView+WebCache.m @@ -18,14 +18,6 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; @implementation UIView (WebCache) -- (nullable NSURL *)sd_imageURL { - return objc_getAssociatedObject(self, @selector(sd_imageURL)); -} - -- (void)setSd_imageURL:(NSURL * _Nullable)sd_imageURL { - objc_setAssociatedObject(self, @selector(sd_imageURL), sd_imageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - - (nullable NSString *)sd_latestOperationKey { return objc_getAssociatedObject(self, @selector(sd_latestOperationKey)); } @@ -34,8 +26,15 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; objc_setAssociatedObject(self, @selector(sd_latestOperationKey), sd_latestOperationKey, OBJC_ASSOCIATION_COPY_NONATOMIC); } +#pragma mark - State + +- (NSURL *)sd_imageURL { + return [self sd_imageLoadStateForKey:self.sd_latestOperationKey].url; +} + - (NSProgress *)sd_imageProgress { - NSProgress *progress = objc_getAssociatedObject(self, @selector(sd_imageProgress)); + SDWebImageLoadState *loadState = [self sd_imageLoadStateForKey:self.sd_latestOperationKey]; + NSProgress *progress = loadState.progress; if (!progress) { progress = [[NSProgress alloc] initWithParent:nil userInfo:nil]; self.sd_imageProgress = progress; @@ -44,7 +43,15 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; } - (void)setSd_imageProgress:(NSProgress *)sd_imageProgress { - objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (!sd_imageProgress) { + return; + } + SDWebImageLoadState *loadState = [self sd_imageLoadStateForKey:self.sd_latestOperationKey]; + if (!loadState) { + loadState = [SDWebImageLoadState new]; + } + loadState.progress = sd_imageProgress; + [self sd_setImageLoadState:loadState forKey:self.sd_latestOperationKey]; } - (nullable id)sd_internalSetImageWithURL:(nullable NSURL *)url @@ -70,7 +77,12 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; } self.sd_latestOperationKey = validOperationKey; [self sd_cancelImageLoadOperationWithKey:validOperationKey]; - self.sd_imageURL = url; + SDWebImageLoadState *loadState = [self sd_imageLoadStateForKey:validOperationKey]; + if (!loadState) { + loadState = [SDWebImageLoadState new]; + } + loadState.url = url; + [self sd_setImageLoadState:loadState forKey:validOperationKey]; SDWebImageManager *manager = context[SDWebImageContextCustomManager]; if (!manager) { @@ -103,7 +115,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; if (url) { // reset the progress - NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress)); + NSProgress *imageProgress = loadState.progress; if (imageProgress) { imageProgress.totalUnitCount = 0; imageProgress.completedUnitCount = 0; @@ -242,7 +254,6 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; - (void)sd_cancelCurrentImageLoad { [self sd_cancelImageLoadOperationWithKey:self.sd_latestOperationKey]; - self.sd_latestOperationKey = nil; } - (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL { diff --git a/SDWebImage/Core/UIView+WebCacheState.h b/SDWebImage/Core/UIView+WebCacheState.h new file mode 100644 index 00000000..f3801c28 --- /dev/null +++ b/SDWebImage/Core/UIView+WebCacheState.h @@ -0,0 +1,62 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import +#import "SDWebImageCompat.h" + +/** + A loading state to manage View Category which contains multiple states. Like UIImgeView.image && UIImageView.highlightedImage + @code + SDWebImageLoadState *loadState = [self sd_imageLoadStateForKey:@keypath(self, highlitedImage)]; + NSProgress *highlitedImageProgress = loadState.progress; + @endcode + */ +@interface SDWebImageLoadState : NSObject + +/** + Image loading URL + */ +@property (nonatomic, strong, nullable) NSURL *url; +/** + Image loading progress. The unit count is the received size and excepted size of download. + */ +@property (nonatomic, strong, nullable) NSProgress *progress; + +@end + +/** + These methods are used for WebCache view which have multiple states for image loading, for example, `UIButton` or `UIImageView.highlightedImage` + It maitain the state container for per-operation, make it possible for control and check each image loading operation's state. + @note For developer who want to add SDWebImage View Category support for their own stateful class, learn more on Wiki. + */ +@interface UIView (WebCacheState) + +/** + Get the image loading state container for specify operation key + + @param key key for identifying the operations + @return The image loading state container + */ +- (nullable SDWebImageLoadState *)sd_imageLoadStateForKey:(nullable NSString *)key; + +/** + Set the image loading state container for specify operation key + + @param state The image loading state container + @param key key for identifying the operations + */ +- (void)sd_setImageLoadState:(nullable SDWebImageLoadState *)state forKey:(nullable NSString *)key; + +/** + Rmove the image loading state container for specify operation key + + @param key key for identifying the operations + */ +- (void)sd_removeImageLoadStateForKey:(nullable NSString *)key; + +@end diff --git a/SDWebImage/Core/UIView+WebCacheState.m b/SDWebImage/Core/UIView+WebCacheState.m new file mode 100644 index 00000000..87355ff6 --- /dev/null +++ b/SDWebImage/Core/UIView+WebCacheState.m @@ -0,0 +1,57 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "UIView+WebCacheState.h" +#import "objc/runtime.h" + +static char loadStateKey; +typedef NSMutableDictionary SDStatesDictionary; + +@implementation SDWebImageLoadState + +@end + +@implementation UIView (WebCacheState) + +- (SDStatesDictionary *)sd_imageLoadStateDictionary { + SDStatesDictionary *states = objc_getAssociatedObject(self, &loadStateKey); + if (!states) { + states = [NSMutableDictionary dictionary]; + objc_setAssociatedObject(self, &loadStateKey, states, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return states; +} + +- (SDWebImageLoadState *)sd_imageLoadStateForKey:(NSString *)key { + if (!key) { + key = NSStringFromClass(self.class); + } + @synchronized(self) { + return [self.sd_imageLoadStateDictionary objectForKey:key]; + } +} + +- (void)sd_setImageLoadState:(SDWebImageLoadState *)state forKey:(NSString *)key { + if (!key) { + key = NSStringFromClass(self.class); + } + @synchronized(self) { + self.sd_imageLoadStateDictionary[key] = state; + } +} + +- (void)sd_removeImageLoadStateForKey:(NSString *)key { + if (!key) { + key = NSStringFromClass(self.class); + } + @synchronized(self) { + self.sd_imageLoadStateDictionary[key] = nil; + } +} + +@end diff --git a/SDWebImage/Private/SDInternalMacros.h b/SDWebImage/Private/SDInternalMacros.h index dfff5585..0b84c68a 100644 --- a/SDWebImage/Private/SDInternalMacros.h +++ b/SDWebImage/Private/SDInternalMacros.h @@ -120,3 +120,68 @@ extern "C" { #if defined(__cplusplus) } #endif + +/** + * \@keypath allows compile-time verification of key paths. Given a real object + * receiver and key path: + * + * @code + +NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String); +// => @"lowercaseString.UTF8String" + +NSString *versionPath = @keypath(NSObject, version); +// => @"version" + +NSString *lowercaseStringPath = @keypath(NSString.new, lowercaseString); +// => @"lowercaseString" + + * @endcode + * + * ... the macro returns an \c NSString containing all but the first path + * component or argument (e.g., @"lowercaseString.UTF8String", @"version"). + * + * In addition to simply creating a key path, this macro ensures that the key + * path is valid at compile-time (causing a syntax error if not), and supports + * refactoring, such that changing the name of the property will also update any + * uses of \@keypath. + */ +#define keypath(...) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Warc-repeated-use-of-weak\"") \ + (NO).boolValue ? ((NSString * _Nonnull)nil) : ((NSString * _Nonnull)@(cStringKeypath(__VA_ARGS__))) \ + _Pragma("clang diagnostic pop") \ + +#define cStringKeypath(...) \ + metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__)) + +#define keypath1(PATH) \ + (((void)(NO && ((void)PATH, NO)), \ + ({ char *__extobjckeypath__ = strchr(# PATH, '.'); NSCAssert(__extobjckeypath__, @"Provided key path is invalid."); __extobjckeypath__ + 1; }))) + +#define keypath2(OBJ, PATH) \ + (((void)(NO && ((void)OBJ.PATH, NO)), # PATH)) + +/** + * \@collectionKeypath allows compile-time verification of key paths across collections NSArray/NSSet etc. Given a real object + * receiver, collection object receiver and related keypaths: + * + * @code + + NSString *employeesFirstNamePath = @collectionKeypath(department.employees, Employee.new, firstName) + // => @"employees.firstName" + + NSString *employeesFirstNamePath = @collectionKeypath(Department.new, employees, Employee.new, firstName) + // => @"employees.firstName" + + * @endcode + * + */ +#define collectionKeypath(...) \ + metamacro_if_eq(3, metamacro_argcount(__VA_ARGS__))(collectionKeypath3(__VA_ARGS__))(collectionKeypath4(__VA_ARGS__)) + +#define collectionKeypath3(PATH, COLLECTION_OBJECT, COLLECTION_PATH) \ + (YES).boolValue ? (NSString * _Nonnull)@((const char * _Nonnull)[[NSString stringWithFormat:@"%s.%s", cStringKeypath(PATH), cStringKeypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String]) : (NSString * _Nonnull)nil + +#define collectionKeypath4(OBJ, PATH, COLLECTION_OBJECT, COLLECTION_PATH) \ + (YES).boolValue ? (NSString * _Nonnull)@((const char * _Nonnull)[[NSString stringWithFormat:@"%s.%s", cStringKeypath(OBJ, PATH), cStringKeypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String]) : (NSString * _Nonnull)nil diff --git a/SDWebImage/include/SDWebImage/UIView+WebCacheState.h b/SDWebImage/include/SDWebImage/UIView+WebCacheState.h new file mode 120000 index 00000000..07799f85 --- /dev/null +++ b/SDWebImage/include/SDWebImage/UIView+WebCacheState.h @@ -0,0 +1 @@ +../../Core/UIView+WebCacheState.h \ No newline at end of file diff --git a/Tests/Tests/SDWebCacheCategoriesTests.m b/Tests/Tests/SDWebCacheCategoriesTests.m index 5f409e25..4d9d45f4 100644 --- a/Tests/Tests/SDWebCacheCategoriesTests.m +++ b/Tests/Tests/SDWebCacheCategoriesTests.m @@ -314,7 +314,6 @@ [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:nil options:0 context:nil setImageBlock:nil progress:nil completed:nil]; [imageView sd_cancelCurrentImageLoad]; NSString *operationKey = NSStringFromClass(UIView.class); - expect(imageView.sd_latestOperationKey).beNil(); expect([imageView sd_imageLoadOperationForKey:operationKey]).beNil(); } diff --git a/WebImage/SDWebImage.h b/WebImage/SDWebImage.h index 9f793c2e..6bc9de80 100644 --- a/WebImage/SDWebImage.h +++ b/WebImage/SDWebImage.h @@ -40,6 +40,7 @@ FOUNDATION_EXPORT const unsigned char SDWebImageVersionString[]; #import #import #import +#import #import #import #import