按照我们调用的顺序,首先要了解UIView+WebCache和UIView+WebCacheOperation,文件中最重要的方法,其他控件方法最终都会调用到此方法。
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
internalSetImageBlock:(nullable SDInternalSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary *)context
方法中主要是3大步骤:
- 取消URL的请求操作。
- 设置占位图,加载&&显示图片。
- 保存第2步的操作对象。
1.取消URL的操作对象
操作的取消是根据方法参数operationKey为key获取集合sd_operationDictionary内的操作对象,然后执行取消操作,并移除操作对象。
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
id operation;
@synchronized (self) {
operation = [operationDictionary objectForKey:key];
}
if (operation) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}
}
此方法在UIView+WebCacheOperation中。这里的operation是要满足< SDWebImageOperation>代理的,对集合操作保证同时只有一个线程在操作。
在这列列举一下视图分类的key分别是什么,
1、UIButton+WebCache的key是UIButtonImageOperation_state,_state是UIControlState类型值(背景图片key是UIButtonBackgroundImageOperation_state)。
2、UIImageView+WebCache的key是UIImageView类名。
3、UIImageView+HighlightedWebCache的key是UIImageViewImageOperationHighlighted。
2.设置占位图并加载图片
设置占位图
options不包含SDWebImageDelayPlaceholder枚举值时,线程安全设置占位图。
if (!(options & SDWebImageDelayPlaceholder)) {
if (group) {
dispatch_group_enter(group);
}
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
占位图内部调用是无动画方式设置图片,除了UIImageView+WebCache直接调用setImage:,另外2个视图分类通过setImageBlock回调设置图片。(具体实现可以找到方法看一下)。
加载图片
这里只讲获取到图片后,显示/回调图片的逻辑。(加载图片的逻辑后面讲)。
这里有2种方式显示图片:
1.将image对象回调到completedBlock显示,条件:
image && (options & SDWebImageAvoidAutoSetImage) 或者
!image && !(options & SDWebImageDelayPlaceholder)
2.UIView+WebCache根据对象类型以及动画对象sd_imageTransition,是否动画显示image,然后再回调completedBlock。
id operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
if (!sself) { return; }
#if SD_UIKIT
[sself sd_removeActivityIndicator];
#endif
// if the progress not been updated, mark it to complete state
if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!sself) { return; }
if (!shouldNotSetImage) {
[sself sd_setNeedsLayout];
}
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, error, cacheType, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
#if SD_UIKIT || SD_MAC
// check whether we should use the image transition
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
transition = sself.sd_imageTransition;
}
#endif
dispatch_main_async_safe(^{
if (group) {
dispatch_group_enter(group);
}
#if SD_UIKIT || SD_MAC
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
if (group) {
// compatible code for FLAnimatedImage, because we assume completedBlock called after image was set. This will be removed in 5.x
BOOL shouldUseGroup = [objc_getAssociatedObject(group, &SDWebImageInternalSetImageGroupKey) boolValue];
if (shouldUseGroup) {
dispatch_group_notify(group, dispatch_get_main_queue(), callCompletedBlockClojure);
} else {
callCompletedBlockClojure();
}
} else {
callCompletedBlockClojure();
}
});
}];
3. 保存第2步的操作对象
根据第 1 步描述的key,先取消操作,再将新的操作对象缓存到sd_operationDictionary内。
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary setObject:operation forKey:key];
}
}
}
}
到了这里主流程UIView+WebCache、UIView+WebCacheOperation就可以结束了。
可以跳转到SDWebImage(二) 查看图片记载的流程。
UIView+WebCache、UIView+WebCacheOperation其他功能方法讲解
UIView+WebCache
1.获取当前图片地址
- (nullable NSURL *)sd_imageURL {
return objc_getAssociatedObject(self, &imageURLKey);
}
2.获取进度对象
- (NSProgress *)sd_imageProgress {
NSProgress *progress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (!progress) {
progress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
self.sd_imageProgress = progress;
}
return progress;
}
- (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
3.取消当前请求操作。
- (void)sd_cancelCurrentImageLoad {
[self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])];
}
这里是根据类名字符串来取消操作,根据上述第一步key的生成,得知,
UIImageView+WebCache才能通过此方法获取操作对象,指向取消功能。
UIButton+WebCache类有根据第 1 步的生成的key取消操作的功能方法。
UIImageView+HighlightedWebCache 是没有取消操作功能的,目前为止此类型图片请求时无法取消的。
4.设置动画对象
/**
The image transition when image load finished. See `SDWebImageTransition`.
If you specify nil, do not do transition. Defautls to nil.
*/
@property (nonatomic, strong, nullable) SDWebImageTransition *sd_imageTransition;
SDWebImageTransition对象详细使用,可以跳转XXXXX
5.ActivityIndicatorView显示/隐藏/style。
- (UIActivityIndicatorView *)activityIndicator {
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
- (void)sd_setShowActivityIndicatorView:(BOOL)show {
objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, @(show), OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)sd_showActivityIndicatorView {
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
}
- (int)sd_getIndicatorStyle{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
}
- (void)sd_addActivityIndicator {
dispatch_main_async_safe(^{
if (!self.activityIndicator) {
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self sd_getIndicatorStyle]];
self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:self.activityIndicator];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
}
[self.activityIndicator startAnimating];
});
}
- (void)sd_removeActivityIndicator {
dispatch_main_async_safe(^{
if (self.activityIndicator) {
[self.activityIndicator removeFromSuperview];
self.activityIndicator = nil;
}
});
}
UIView+WebCacheOperation
这里的功能是sd_operationDictionary对象,对operation对象的添加、取消操作功能。通过NSMapTable集合的NSPointerFunctionsStrongMemory、NSPointerFunctionsWeakMemory,对key是copy,对value是弱引用。通过@synchronized对集合对象线程安全操作。
static char loadOperationKey;
// key is copy, value is weak because operation instance is retained by SDWebImageManager's runningOperations property
// we should use lock to keep thread-safe because these method may not be acessed from main queue
typedef NSMapTable> SDOperationsDictionary;
@implementation UIView (WebCacheOperation)
- (SDOperationsDictionary *)sd_operationDictionary {
@synchronized(self) {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
return operations;
}
operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
}
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary setObject:operation forKey:key];
}
}
}
}
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
id operation;
@synchronized (self) {
operation = [operationDictionary objectForKey:key];
}
if (operation) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}
}
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}
到这里UIView+WebCache 和UIView+WebCacheOperation 的功能就讲完了。