該文章閱讀的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