本次学习从SDWebImage 常用的图片加载开始 ,由浅入深的剖析实现过程
UIImageView *imgV = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[imgV sd_setImageWithURL:[NSURL URLWithString:@""] placeholderImage:nil];
首先进入方法 ,该方法是UIImageView 的分类中实现 UIImageView+WebCache.h
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
继续深入
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
// 此处为UIImageView 的父类UIView 的分类方法 UIView+WebCache
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:nil
progress:progressBlock
completed:completedBlock];
}
查看该方法详细实现。分析见注释
- (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 {
// 首先会根据operationKey 传入的值来设置validOperationKey,当为nil时,设置为当前类名的字符串
// 我们在调用 sd_setImageWithURL: placeholderImage: 时,这里传入的是nil 所以此处validOperationKey = @"UIImageView"
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
// 该方法将根据validOperationKey的值去取消对应的图片加载任务
// 该方法的详细实现在下面
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
// 将当前url 通过imageURLKey 与self 绑定,由于self不同,这样就实现了 url 与对应的UIImageView绑定。
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 在本例的简单调用中options = 0,所以 判断为真,进入分支
// dispatch_main_async_safe() 这个宏是为了判断是否在主线程,如果不在则回到主线程调用。其实现见下
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
// 确保在主线程下调用
// 此处由参数可见 是为了先设置placeholder 即占位图,实现没有图片时显示占位图,本例中 placeholder,setImageBlock 参数均为nil
// 该方法实现见下
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
id operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
if (!sself) {
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[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);
}
});
}
}
dispatch_main_async_safe() 的实现,
dispatch_queue_get_label获取当前队列标签,使用strcmp()函数和主线程队列标签比较
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif
该方法实现在UIView的分类UIView+WebCacheOperation
typedef NSMutableDictionary<NSString *, id> SDOperationsDictionary;
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self operationDictionary];
id operations = operationDictionary[key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}
- (SDOperationsDictionary *)operationDictionary {
// 通过变量绑定的方式,获取到operations
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
return operations;
}
// 第一次进入时,operations需要新创建,并进行变量绑定给loadOperationKey,然后返回
operations = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
if (setImageBlock) { // 本例中为nil
setImageBlock(image, imageData);
return;
}
// 下面代码就比较简单了,根据self的类执行不同方法设置占位图。
#if SD_UIKIT || SD_MAC // 平台兼容适配宏
if ([self isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = (UIImageView *)self;
imageView.image = image;
}
#endif
#if SD_UIKIT
if ([self isKindOfClass:[UIButton class]]) {
UIButton *button = (UIButton *)self;
[button setImage:image forState:UIControlStateNormal];
}
#endif
}