Add better support for stateful view (UIButton) for image URL/progress state management

Key idea:
1. Image Loading pipeline is based on `operation key`, when start/cancel it always need operation key
2. Stateful view has multiple operation key for different image property, we can use KeyPath as the hint to generate operation key
3. To make exists API compatible, we do not change much, however, in 6.0 we may do refactory and store all the loadState/operation/result into the single object, instead of 3
This commit is contained in:
DreamPiggy 2023-07-30 15:57:11 +08:00
parent 8dbf8ed97a
commit a986a77734
15 changed files with 353 additions and 113 deletions

View File

@ -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 = "<group>"; };
320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageRep.m; path = Core/SDAnimatedImageRep.m; sourceTree = "<group>"; };
320797422A76287D00B17CF5 /* UIView+WebCacheState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView+WebCacheState.h"; path = "Core/UIView+WebCacheState.h"; sourceTree = "<group>"; };
320797432A76287D00B17CF5 /* UIView+WebCacheState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIView+WebCacheState.m"; path = "Core/UIView+WebCacheState.m"; sourceTree = "<group>"; };
320CAE132086F50500CFFC80 /* SDWebImageError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageError.h; path = Core/SDWebImageError.h; sourceTree = "<group>"; };
320CAE142086F50500CFFC80 /* SDWebImageError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageError.m; path = Core/SDWebImageError.m; sourceTree = "<group>"; };
321117A7296573680001FC2C /* SDCallbackQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDCallbackQueue.h; path = Core/SDCallbackQueue.h; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 */,

View File

@ -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

View File

@ -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.
*

View File

@ -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<NSString *, NSURL *> 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<SDWebImageIndicator> sd_imageIndicator;

View File

@ -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<SDWebImageOperation>)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 {

View File

@ -0,0 +1,62 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#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

View File

@ -0,0 +1,57 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* 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<NSString *, SDWebImageLoadState *> 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

View File

@ -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

View File

@ -0,0 +1 @@
../../Core/UIView+WebCacheState.h

View File

@ -40,6 +40,7 @@ FOUNDATION_EXPORT const unsigned char SDWebImageVersionString[];
#import <SDWebImage/UIButton+WebCache.h>
#import <SDWebImage/SDWebImagePrefetcher.h>
#import <SDWebImage/UIView+WebCacheOperation.h>
#import <SDWebImage/UIView+WebCacheState.h>
#import <SDWebImage/UIImage+Metadata.h>
#import <SDWebImage/UIImage+MultiFormat.h>
#import <SDWebImage/UIImage+MemoryCacheCost.h>