用过SDWebImage (下文用 SD 代指)的人都知道,这是一款可以通过分类的形式来为 UIImageView 以及 UIButton 异步加载图片,并提供缓存的一个开源库。SD 有很多优点,可以在 Github 上看到,为了读者方便,这里做搬运工作:
- [x] 异步加载图片,并支持缓存
- [x] 两级缓存方案,内存缓存 + 磁盘缓存
- [x] 子线程为图片解码,解压
- [x] 同一个 URL 对应的图片可以保证不会进行多次下载
- [x] 保证绝对不会卡主线程
- [x] 保证加载失败的图片地址不会加载一遍又一遍
- [x] 支持 GCD 和 ARC
- [x] 支持多种图片格式 GIF,JPEG,PNG,以及 WebP
先来一个官方的时序图(如果不清楚时序图是什么意思的可以去看我的另一篇 UML使用-时序图),大致介绍一下整体的一个工作流程
简要整个图片的介绍流程:
-
1.UIImageView 调用分类 UIImageView+WebCache 的
sd_setImageWithURL
方法(当然不止这一个方法,在这个分类的.h文件下可以看到)作为入口来加载图片。 -
2.然后 UIImageView 的分类其实是调用的其父类UIView的分类 UIView+WebCache 的
sd_internalSetImageWithURL
方法,这里为什么要把这个调用放到父类UIView里面呢,
因为 SDWebImage 框架也支持 UIButton 的下载图片等方法,所以需要在它们的父类:UIView里面统一给一个下载方法。 -
3.UIView+WebCache 调用 SDWebImageManager 的
loadImageWithURL
来真正加载图片。加载图片分为两步,首先先调用 SDImageCache 来查询缓存,如果查询缓存命中的话,返回 image;如果查询缓存没有的话,此时再通过 SDImageDownloader 调用downloadImage
来从服务端下载图片,下载成功之后先更新到缓存,再返回 image。
因此,文章将分成三个部分来介绍整个 SD 框架 ,大致按照时序图上面的步骤分成以下三个部分,本文是第一篇
-
1.框架的基本上手与图片的加载,即主要对 UIImageView(WebCache) ,UIView(WebCache)
-
2.对图片加载过程中涉及到 SDWebImageManager 来介绍,以及进行介绍 SDImageCache及其相关的东西
-
3.对缓存查询失败之后去网上下载,以及下载后的操作来介绍,即 SDWebImageDownloader及 其一些相关的东西
到目前为止,咱们对 SD 也有了一个大致一点的认识,接下来咱们开始对第一部分做介绍吧:
SD暴露给我们用的最外层的就 UIImageView(WebCache),我们可以通过调用不同的接口,将图片地址,占位图,加载进度回调,完成回调直接给这个类,下面是这个类的公共接口:
/* =========================== UIImageView + WebCache.h ====================== */
- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options;
- (void)sd_setImageWithURL:(nullable NSURL *)url
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
-(void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
//加载多张图片用来轮播
- (void)sd_setAnimationImagesWithURLs:(nonnull NSArray<NSURL *> *)arrayOfURLs;
- (void)sd_cancelCurrentAnimationImagesLoad;
可以看到这个类里面各个方法最后都会跳到父类的这个方法:
/* ============================ UIView + WebCache.h ========================== */
- (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的作用:理解这个变量,估计要往后多看一部分代码才能明白,这里先做一点讲解。UIImageView(WebCache) 这个分类会为UIImageView添加一个字典SDOperationsDictionary作为其属性(不是说分类里面不能加吗,这是怎么搞的?别着急,后面会讲),字典的key 就是这个operationKey ,而字典的value就是一个operation, 这个operation代表了一个加载图片这样一个操作(将这个operation 和 多线程里面的 NSOperation
联系起来会更加好理解)。所以这个 operationKey 其实是用来作为字典的键的,方便以后通过这样一个key来找到一个ImageView的实例的拥有的那些加载图片的operation。(其实后面找这个operation的目的也就是为了在特地的时候好用来cancle掉)
SDWebImageOptions:这是一个放置在 SDWebImageManager.h 中的枚举,姑且可以理解为指定图片加载的一些策略吧,下面一个一个介绍
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
//默认情况下,如果一张图片下载失败了,这个图片对应的URL是会被添加到黑名单中的,这样可以保证一个失败的URL不会反复被加载;而这个选项代表的是这个URL即使下载失败了也不会放到黑名单中
SDWebImageRetryFailed = 1 << 0,
//默认情况下,图片的下载会在UI交互的过程中开始,这个枚举值所代表的就是推迟下载比如在UIScrollView滚动减速之后
SDWebImageLowPriority = 1 << 1,
//这个枚举值限定缓存只在内存中,不缓存到磁盘上去
SDWebImageCacheMemoryOnly = 1 << 2,
//默认情况下,一张图片只会在数据完全下载回来之后才会显示,这个枚举值,可以让图片一点一点的随着下载回来的数据显示
SDWebImageProgressiveDownload = 1 << 3,
// 磁盘缓存将会由NSURLCache 而不是SDWebImage 来处理,因此可能带来轻微的性能下降。
// 使用于使用固定的图片url 但是图片内容可能变化的场景
SDWebImageRefreshCached = 1 << 4,
// 如果应用进入后台状态,继续图片下载,应用因此将会额外活跃一段时间,
// 如果这段时间用完但是下载任务尚未完成,那么下载就会被取消
SDWebImageContinueInBackground = 1 << 5,
// 可以控制存在NSHTTPCookieStore的cookies.
SDWebImageHandleCookies = 1 << 6,
// 允许不受信任的SSL证书。主要用于测试目的。正式环境中慎用
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
//默认情况下,image在加载的时候是按照他们在队列中的顺序装载的(就是先进先出).这个flag会把当前图片加载移动到队列的前端,立刻加载
SDWebImageHighPriority = 1 << 8,
//默认情况下,占位图是在图片加载的时候加载的,这个标记会延迟加载,直到图片加载完成
SDWebImageDelayPlaceholder = 1 << 9,
//是否transform animatedImage
SDWebImageTransformAnimatedImage = 1 << 10,
//默认情况下,下载的image是直接加到ImageView上的,但是在某些情况下,我们希望在加到ImageView上之前做一些操作,通过这是这个枚举值,你就可以在下载成功回调里面先做你想做的,在添加到ImageView上了
SDWebImageAvoidAutoSetImage = 1 << 11,
//图片默认会被解码成它们的原始尺寸。这个flag 会将图片按照设备的内存来进行缩放。如果SDWebImageProgressiveDownload 被设置了,那么这个选项就不起作用
SDWebImageScaleDownLargeImages = 1 << 12
};
下面咱们详细看一下这个方法的实现:我们尽量先把整个逻辑理清楚,例如有些方法我们只介绍方法干嘛,暂时不跳进去详细介绍。适当时候在详细分析
/* ========================== UIView + WebCache.h =========================== */
- (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 {
//先找到指定的key,如果key没有的话,就用类名作为key
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
//根据key找到所有的 Operation 然后 cancle掉
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
//用runtime把当前图片的url绑定到当前的imageView上,为当前imageView添加一个url属性
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//如果options的选项不是延迟加载占位图的话,就加载占位图
if (!(options & SDWebImageDelayPlaceholder)) {
//`dispatch_main_async_safe`可以去看到其实是一个简单的封装,就是操作移到主线程来更新UI
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
//如果加载过程中需要显示菊花的话就显示菊花,默认不显示
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
//这里很明显是调用 SDWebImageManager 去加载图片(注意咱们这里的加载是包括了去缓存找以及如果缓存没找到去下载的过程),函数返回一个实现了 SDWebImageOperation 的协议的这样一个对象,目的在于以后便于统一去cancle缓存查找和图片下载
id <SDWebImageOperation> 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) {
//image存在,且下载完成后不自动将image设置到ImageView上,让开发者在成功回调里面自己去设置
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
//image存在,且下载完成后是自动将image设置到ImageView上
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
//image不存在,如果option恰好也是延迟设置,只能把占位图放上去了
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
//有回调办法 && 加载完成
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
//在表示正在进行的图片加载字典(operationDictionary)里添加operation
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
//URL不存在的情况
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);
}
});
}
}
几个重要的点:
1.属性绑定:属性绑定其实就是通过runtime动态的为一个对象添加属性,通过 Associated Objects 就可以为分类添加属性了,解决分类不能添加属性的问题,在 UIView+WebCache 中就通过这个方式为 UIImageView 绑定了一个URL。有关于属性绑定的更多信息可以参考这篇博客这里就不做过多介绍了。
在UIView+WebCache
的使用:
//绑定的key
static char imageURLKey;
//将url绑定到self,也就是ImageView上
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//通过ImageView 获取他绑定的URL
- (nullable NSURL *)sd_imageURL {
return objc_getAssociatedObject(self, &imageURLKey);
}
2.dispatch_main_async_safe:
关于这个宏的定义可以在 SDWebImageCompat.h
中看到:
#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
这段代码的目的在前面已经提到,这段代码会把当前的操作移到主队列中执行;具体的操作则是判断当前执行任务的队列是不是主队列,如果是的话,就直接把操作添加到当前队列;如果不是的,就把要执行的任务加到主队列中执行。
总结:在这一篇中,咱们主要介绍了SD中上层UIKit的一些操作,大体介绍了整个SD的工作流程,让读者对SD有个比较全面的认识,并且在这儿也介绍了一些重要的知识点;在下一篇中 ,将主要详细介绍SDWebImageManager,以及SDWebImageCache这两个非常主要的类所做的操作。
网友评论