在iOS的图片加载框架中,SDWebImage使用频率非常高。它支持从网络中下载且缓存图片,并设置图片到对应的UIImageView控件或者UIButton控件。在项目中使用SDWebImage来管理图片加载相关操作可以极大地提高开发效率,让我们更加专注于业务逻辑实现。
SDWebImage 概论
SDWebImage是个支持异步下载与缓存的UIImageView扩展。项目主要提供了一下功能:
1.提供了一个UIImageView的category用来加载网络图片并且对网络图片的缓存进行管理
2.采用异步方式来下载网络图片
3.采用异步方式,使用内存+磁盘来缓存网络图片,拥有自动的缓存过期处理机制。
4.支持GIF动画
5.支持WebP格式
6.同一个URL的网络图片不会被重复下载
7.失效,虚假的URL不会被无限重试
8.耗时操作都在子线程,确保不会阻塞主线程
9.使用GCD和ARC
10.支持Arm64
11.支持后台图片解压缩处理
12.项目支持的图片格式包括 PNG,JPEG,GIF,Webp等
关键类讲解
SDWebImageDownloader
:负责维持图片的下载队列;SDWebImageDownloaderOperation
:负责真正的图片下载请求;SDImageCache
:负责图片的缓存;SDWebImageManager
:是总的管理类,维护了一个SDWebImageDownloader实例和一个SDImageCache实例,是下载与缓存的桥梁;SDWebImageDecoder
:负责图片的解压缩;SDWebImagePrefetcher
:负责图片的预取;UIImageView+WebCache
:和其他的扩展都是与用户直接打交道的。
UIImageView+WebCache
这里只用UIImageView+WebCache
来举个例子,其他的扩展类似。
使用场景:已知图片的url地址,下载图片并设置到UIImageView上。
UIImageView+WebCache提供了一系列的接口:
- (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_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
新版本还给UIView增加了分类,即UIView+WebCache
,最终上述方法会走到下面的方法去具体操作,比如下载图片等。
- (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;
接下来对该方法进行解析
- 第一步:取消当前正在进行的异步下载,确保每个 UIImageView 对象中永远只存在一个 operation,当前只允许一个图片网络请求,该 operation 负责从缓存中获取 image 或者是重新下载 image。具体执行代码是:
-
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]); // 取消先前下载的任务 [self sd_cancelImageLoadOperationWithKey:validOperationKey]; ... // 下载图片操作 // 将生成的加载操作赋值给UIView的自定义属性 [self sd_setImageLoadOperation:operation forKey:validOperationKey];
上述方法定义在
UIView+WebCacheOperation
类中 -
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)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]; // 获取添加在UIView的自定义属性 id<SDWebImageOperation> operation; @synchronized (self) { operation = [operationDictionary objectForKey:key]; } if (operation) { // 实现了SDWebImageOperation的协议 if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) { [operation cancel]; } @synchronized (self) { [operationDictionary removeObjectForKey:key]; } } } }
实际上,所有的操作都是由一个实际上,所有的操作都是由一个
operationDictionary
字典维护的,执行新的操作之前,cancel所有的operation。 - 第二步:占位图策略
作为图片下载完成之前的替代图片。dispatch_main_async_safe
是一个宏,保证在主线程安全执行。if (!(options & SDWebImageDelayPlaceholder)) { dispatch_main_async_safe(^{ // 设置占位图 [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock]; }); }
- 第三步:判断url是否合法
如果url合法,则进行图片下载操作,否则直接block回调失败if (url) { // 下载图片操作 } else { dispatch_main_async_safe(^{ #if SD_UIKIT [self sd_removeActivityIndicator]; #endif if (completedBlock) { NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}]; completedBlock(nil, error, SDImageCacheTypeNone, url); } }); }
- 第四步 下载图片操作
下载图片的操作是由SDWebImageManager
完成的,它是一个单例- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock;
下载完成之后刷新UIImageView的图片。
// 根据枚举类型,判断是否需要设置图片 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) { // 不要自动设置图片,则调用block传入image对象 dispatch_main_async_safe(callCompletedBlockClojure); return; } // 设置图片操作 dispatch_main_async_safe(^{ #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]; #endif callCompletedBlockClojure(); });
最后,把返回的id operation添加到operationDictionary中,方便后续的cancel。
// 将生成的加载操作赋值给UIView的自定义属性 [self sd_setImageLoadOperation:operation forKey:validOperationKey];
SDWebImageManager
在SDWebImageManager.h中是这样描述SDWebImageManager类的:
/**
* The SDWebImageManager is the class behind the UIImageView+WebCache category and likes.
* It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache).
* You can use this class directly to benefit from web image downloading with caching in another context than
* a UIView.
*/
即隐藏在UIImageView+WebCache背后,用于处理异步下载和图片缓存的类,当然你也可以直接使用 SDWebImageManager 的方法 来直接下载图片。
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
SDWebImageManager.h首先定义了一些枚举类型的SDWebImageOptions
。
然后,声明了四个block:
//操作完成的回调,被上层的扩展调用。
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
//被SDWebImageManager调用。如果使用了SDWebImageProgressiveDownload标记,这个block可能会被重复调用,直到图片完全下载结束,finished=true,再最后调用一次这个block。
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
//SDWebImageManager每次把URL转换为cache key的时候调用,可以删除一些image URL中的动态部分。
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL);
定义了SDWebImageManagerDelegate协议:
@protocol SDWebImageManagerDelegate
@optional
// 控制在cache中没有找到image时 是否应该去下载。
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
// 在下载之后,缓存之前转换图片。在全局队列中操作,不阻塞主线程
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
@end
SDWebImageManager是单例使用的,分别维护了一个SDImageCache实例和一个SDWebImageDownloader实例。 对象方法分别是:
//初始化SDWebImageManager单例,在init方法中已经初始化了cache单例和downloader单例。
- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader;
//下载图片
- (id )downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
//缓存给定URL的图片
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
//取消当前所有的操作
- (void)cancelAll;
//监测当前是否有进行中的操作
- (BOOL)isRunning;
//监测图片是否在缓存中, 先在memory cache里面找 再到disk cache里面找
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
//监测图片是否缓存在disk里
- (BOOL)diskImageExistsForURL:(NSURL *)url;
//监测图片是否在缓存中,监测结束后调用completionBlock
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//监测图片是否缓存在disk里,监测结束后调用completionBlock
- (void)diskImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//返回给定URL的cache key
- (NSString *)cacheKeyForURL:(NSURL *)url;