该文章阅读的SDWebImage的版本为4.3.3。
1.全局静态常量
/**
保存自定义调度组的key
*/
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageInternalSetImageGroupKey;
复制代码
/**
保存自定义图像管理者的key
*/
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageExternalCustomManagerKey;
复制代码
/**
获取不到进度时的默认数值,为 long long 类型的1
*/
FOUNDATION_EXPORT const int64_t SDWebImageProgressUnitCountUnknown;
复制代码
2.公共类型定义
/**
用于自定义图像设置的block
*/
typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData);
复制代码
3.公共属性
/**
图像加载进度
*/
@property (nonatomic, strong, null_resettable) NSProgress *sd_imageProgress;
复制代码
/**
图像加载完成时的转换动画对象
*/
@property (nonatomic, strong, nullable) SDWebImageTransition *sd_imageTransition;
复制代码
3.公共方法
/**
获取当前加载的url
*/
- (nullable NSURL *)sd_imageURL;
复制代码
/**
为控件设置图像的方法
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
复制代码
/**
为控件设置图像的方法,比上一个方法多了一个context参数
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary<NSString *, id> *)context;
复制代码
/**
取消当前图像加载
*/
- (void)sd_cancelCurrentImageLoad;
复制代码
/**
是否展示UIActivityIndicatorView
*/
- (void)sd_setShowActivityIndicatorView:(BOOL)show;
复制代码
/**
设置UIActivityIndicatorView的类型
*/
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style;
复制代码
/**
获取UIActivityIndicatorView的展示状态
*/
- (BOOL)sd_showActivityIndicatorView;
复制代码
/**
添加UIActivityIndicatorView
*/
- (void)sd_addActivityIndicator;
复制代码
/**
移除UIActivityIndicatorView
*/
- (void)sd_removeActivityIndicator;
复制代码
4.私有静态变量
/**
用于保存图像路径
*/
static char imageURLKey;
复制代码
/**
用于保存加载小菊花
*/
static char TAG_ACTIVITY_INDICATOR;
复制代码
/**
用于保存加载小菊花的类型
*/
static char TAG_ACTIVITY_STYLE;
复制代码
/**
用于保存加载小菊花的展示状态
*/
static char TAG_ACTIVITY_SHOW;
复制代码
5.实现
5.1.私有方法
/**
向控件上设置图像
*/
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
// 调用下面的图像设置方法
[self sd_setImage:image imageData:imageData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:nil cacheType:0 imageURL:nil];
}
复制代码
/**
向控件上设置图像
*/
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock transition:(SDWebImageTransition *)transition cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL {
// 获取当前视图对象
UIView *view = self;
// 创建变量保存设置图像操作
SDSetImageBlock finalSetImageBlock;
if (setImageBlock) {
// 如果设置了就直接保存
finalSetImageBlock = setImageBlock;
}
#if SD_UIKIT || SD_MAC
else if ([view isKindOfClass:[UIImageView class]]) {
// 如果没设置,但是是UIImageView类型,就在block中设置图像
UIImageView *imageView = (UIImageView *)view;
finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData) {
imageView.image = setImage;
};
}
#endif
#if SD_UIKIT
else if ([view isKindOfClass:[UIButton class]]) {
// 如果没设置,但是是UIButton类型,就在block中为UIControlStateNormal状态设置图像
UIButton *button = (UIButton *)view;
finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData){
[button setImage:setImage forState:UIControlStateNormal];
};
}
#endif
if (transition) {
#if SD_UIKIT
// 如果设置了展示动画就添加动画
[UIView transitionWithView:view duration:0 options:0 animations:^{
// 0 duration to let UIKit render placeholder and prepares block
if (transition.prepares) {
transition.prepares(view, image, imageData, cacheType, imageURL);
}
} completion:^(BOOL finished) {
[UIView transitionWithView:view duration:transition.duration options:transition.animationOptions animations:^{
if (finalSetImageBlock && !transition.avoidAutoSetImage) {
finalSetImageBlock(image, imageData);
}
if (transition.animations) {
transition.animations(view, image);
}
} completion:transition.completion];
}];
#elif SD_MAC
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull prepareContext) {
// 0 duration to let AppKit render placeholder and prepares block
prepareContext.duration = 0;
if (transition.prepares) {
transition.prepares(view, image, imageData, cacheType, imageURL);
}
} completionHandler:^{
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
context.duration = transition.duration;
context.timingFunction = transition.timingFunction;
context.allowsImplicitAnimation = (transition.animationOptions & SDWebImageAnimationOptionAllowsImplicitAnimation);
if (finalSetImageBlock && !transition.avoidAutoSetImage) {
finalSetImageBlock(image, imageData);
}
if (transition.animations) {
transition.animations(view, image);
}
} completionHandler:^{
if (transition.completion) {
transition.completion(YES);
}
}];
}];
#endif
} else {
// 如果没设置动画就直接设置图像
if (finalSetImageBlock) {
finalSetImageBlock(image, imageData);
}
}
}
复制代码
/**
更新布局
*/
- (void)sd_setNeedsLayout {
// 兼容不同系统
#if SD_UIKIT
[self setNeedsLayout];
#elif SD_MAC
[self setNeedsLayout:YES];
#endif
}
复制代码
- (int)sd_getIndicatorStyle{
// 通过关联对象获取到加载小菊花对象类型
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
}
复制代码
5.2.自定义getter/setter方法
- (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);
}
复制代码
- (SDWebImageTransition *)sd_imageTransition {
// 通过关联对象获取到展示动画对象
return objc_getAssociatedObject(self, @selector(sd_imageTransition));
}
- (void)setSd_imageTransition:(SDWebImageTransition *)sd_imageTransition {
// 通过关联对象保存展示动画对象
objc_setAssociatedObject(self, @selector(sd_imageTransition), sd_imageTransition, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
复制代码
- (UIActivityIndicatorView *)activityIndicator {
// 通过关联对象获取到加载小菊花对象
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
// 通过关联对象保存加载小菊花对象
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
复制代码
5.3.公共方法
- (nullable NSURL *)sd_imageURL {
// 通过关联对象获取到图像路径
return objc_getAssociatedObject(self, &imageURLKey);
}
复制代码
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
// 调用下面的全能方法
return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
}
复制代码
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary<NSString *, id> *)context {
// 创建变量保存key,如果没有就用当前类名
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
// 取消掉key对应的图像加载操作
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
// 通过关联对象保存图像路径
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 如果没选择延迟加载占位图的选项
if (!(options & SDWebImageDelayPlaceholder)) {
// 如果自定义了调度组就添加任务
if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
dispatch_group_enter(group);
}
// 主队列异步调用
dispatch_main_async_safe(^{
// 设置占位图
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
// 如果传入了图像路径
// 如果要展示加载小菊花就添加加载小菊花控件
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
// 重置进度数据
self.sd_imageProgress.totalUnitCount = 0;
self.sd_imageProgress.completedUnitCount = 0;
// 获取图像管理者对象
SDWebImageManager *manager;
// 如果自定义了图像管理者对象就用自定义的,否则就用单例对象
if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
} else {
manager = [SDWebImageManager sharedManager];
}
// 创建代码块用来处理进度数据
__weak __typeof(self)wself = self;
SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
// 保存进度数据
wself.sd_imageProgress.totalUnitCount = expectedSize;
wself.sd_imageProgress.completedUnitCount = receivedSize;
// 回调进度数据
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
// 创建图像加载操作
id <SDWebImageOperation> 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; }
// 移除加载小菊花
[sself sd_removeActivityIndicator];
// 如果完成但是没有进度数据,就为进度数据设置默认值
if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
// 如果完成,或者没完成但是设置了手动设置图像的选项,就需要调用完成block
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);
}
};
如果不需要设置图像就直接回调完成情况
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
// 创建变量保存图像对象和图像数据
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// 如果有图就直接保存
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// 如果没图但是设置了延迟加载占位图的选项,就职保存占位图
targetImage = placeholder;
targetData = nil;
}
// 创建变量保存图片加载动画对象
SDWebImageTransition *transition = nil;
// 如果加载完成,并且设置了强制展示动画选项,
// 或者加载完成,并且没设置强制展示动画选项但是是从网络下载的图像
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
// 获取图片加载动画对象
transition = sself.sd_imageTransition;
}
if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
// 如果自定义了调度组就添加任务
dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
dispatch_group_enter(group);
// 主线程异步调用设置图像
dispatch_main_async_safe(^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
});
// 调度完成后调用完成回调block
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
callCompletedBlockClojure();
});
} else {
// 正常情况下就直接主线程异步调用设置图像并回调完成情况
dispatch_main_async_safe(^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
callCompletedBlockClojure();
});
}
}];
// 将key与图像加载操作相关联
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
// 如果没有传图像路径
// 主队列异步调用
dispatch_main_async_safe(^{
// 移除加载小菊花
[self sd_removeActivityIndicator];
// 回调错误信息
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
复制代码
- (void)sd_cancelCurrentImageLoad {
// 调用UIView+WebCacheOperation分类方法取消当前控件的图像加载操作
[self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])];
}
复制代码
- (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);
}
复制代码
- (void)sd_addActivityIndicator {
#if SD_UIKIT
// 主队列异步调用
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];
});
#endif
}
复制代码
- (void)sd_removeActivityIndicator {
#if SD_UIKIT
// 主队列异步调用
dispatch_main_async_safe(^{
// 如果有加载小菊花对象就移除并将保存的属性置空
if (self.activityIndicator) {
[self.activityIndicator removeFromSuperview];
self.activityIndicator = nil;
}
});
#endif
}
复制代码
6.总结
这个分类实现了向控件上设置图像的基本操作,其他的分类,如UIImageView+WebCache
、UIButton+WebCache
等基本上没有做什么额外的操作,只是调用该分类的方法。
源码阅读系列:SDWebImage
源码阅读:SDWebImage(二)——SDWebImageCompat
源码阅读:SDWebImage(三)——NSData+ImageContentType
源码阅读:SDWebImage(四)——SDWebImageCoder
源码阅读:SDWebImage(五)——SDWebImageFrame
源码阅读:SDWebImage(六)——SDWebImageCoderHelper
源码阅读:SDWebImage(七)——SDWebImageImageIOCoder
源码阅读:SDWebImage(八)——SDWebImageGIFCoder
源码阅读:SDWebImage(九)——SDWebImageCodersManager
源码阅读:SDWebImage(十)——SDImageCacheConfig
源码阅读:SDWebImage(十一)——SDImageCache
源码阅读:SDWebImage(十二)——SDWebImageDownloaderOperation
源码阅读:SDWebImage(十三)——SDWebImageDownloader
源码阅读:SDWebImage(十四)——SDWebImageManager
源码阅读:SDWebImage(十五)——SDWebImagePrefetcher
源码阅读:SDWebImage(十六)——SDWebImageTransition
源码阅读:SDWebImage(十七)——UIView+WebCacheOperation
源码阅读:SDWebImage(十八)——UIView+WebCache
源码阅读:SDWebImage(十九)——UIImage+ForceDecode/UIImage+GIF/UIImage+MultiFormat
源码阅读:SDWebImage(二十)——UIButton+WebCache
源码阅读:SDWebImage(二十一)——UIImageView+WebCache/UIImageView+HighlightedWebCache