SDWebImage源码阅读(一)--基础架构
SDWebImage架构图
SDWebImage的UML类图,具体UML类图的阅读规则,可以参考这篇文章:UML基本使用--类图

以下为SDWebImage时序图,具体时序图的阅读规则,可以参考这篇文章:UML基本使用--时序图

目录结构
- Downloader
SDWebImageDownloaderOptions
SDWebImageDownloaderOperation
- Cache
SDImageCache
SDImageCacheConfig
- Decoder
SDWebImageCodersManager
SDWebImageCoder
SDWebImageImageIOCoder
SDWebImageGIFCoder
SDWebImageWebPCoder
SDWebImageFrame
SDWebImageCoderHelper
SDAnimatedImageRep
- Utils
SDWebImageManager
SDWebImagePrefetcher
SDWebImageTransition
- Categories
NSData+ImageContentType
UIImage+GIF
UIImage+MultiFormat
UIImage+WebP
UIImage+ForceDecode
UIView+WebCacheOperation
UIImage+MemoryCacheCost
- WebCache Categories
NSButton+WebCache
MKAnnotationView+WebCache
UIButton+WebCache
UIImageView+HighlightedWebCache
UIImageView+WebCache
UIView+WebCache
- FLAnimatedImage
FLAnimatedImageView+WebCache
- 其他
SDWebImageCompat
SDWebImageOperation
具体解释请看下表:
类名 | 功能 |
---|---|
SDWebImageDownloader |
是专门用来下载图片和优化图片加载的,跟缓存没有关系 |
SDWebImageDownloaderOperation |
继承于NSOperation ,用来处理下载任务的 |
SDImageCache |
用来处理内存缓存和磁盘缓存(可选)的, 其中磁盘缓存是异步进行的,因此不会阻塞主线程 |
SDWebImageManager |
作为 UIImageView+WebCache 背后的默默付出者,主要功能是将图片下载(SDWebImageDownloader )和图片缓存(SDImageCache )两个独立的功能组合起来 |
SDWebImageDecoder |
图片解码器,用于图片下载完成后进行解码 |
SDWebImagePrefetcher |
预下载图片,方便后续使用,图片下载的优先级低,其内部由SDWebImageManager 来处理图片下载和缓存 |
UIView+WebCacheOperation |
用来记录图片加载的operation,方便需要时取消和移除图片加载的operation |
UIImageView+WebCache |
集成SDWebImageManager 的图片下载和缓存功能到UIImageView 方法中,方便调用 |
UIImageView+HighlightedWebCache |
跟 UIImageView+WebCache 类似,也是包装了 SDWebImageManager ,只不过是用于加载 highlighted 状态的图片 |
UIButton+WebCache |
跟 UIImageView+WebCache 类似,集成 SDWebImageManager 的图片下载和缓存功能到 UIButton 的方法中,方便调用 |
MKAnnotationView+WebCache |
跟 UIImageView+WebCache 类似 |
NSData+ImageContentType |
用于获取图片数据的格式(JPEG、PNG等) |
UIImage+GIF |
用于加载 GIF 动图 |
UIImage+MultiFormat |
根据不同格式的二进制数据转成 UIImage 对象 |
UIImage+WebP |
用于解码并加载WebP 图片 |
基本流程分析
下面我们从UIImageView+WebCache
的加载图片方法开始,通过源码来分下下图片加载的流程:
/**
利用url结合一些自定义选项给UIImageView设置图片
@param url 网络链接
@param placeholder 初始化图片
@param options SDWebImageOptions类 请求的可选项类
@param progressBlock 下载进度回调
@param completedBlock 完成回调
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
上面是UIImageView+WebCache
的加载图片方法,我们分析下参数。我们先看下options
,这个SDWebImageOptions
是个NS_OPTIONS
类。用来设置请求可选项参数。我们跳进去看一下,代码如下(已注释):
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**
* By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying.
* This flag disable this blacklisting.
默认情况下,当一个URL下载失败时,该URL会被列入黑名单,这样库就不会继续尝试。
开启这个选项,则不会列入黑名单
*/
SDWebImageRetryFailed = 1 << 0,
/**
* By default, image downloads are started during UI interactions, this flags disable this feature,
* leading to delayed download on UIScrollView deceleration for instance.
默认情况下,图像下载是在UI交互期间启动的,此标志禁用此功能,
例如导致UIScrollview减速时下载延迟。
*/
SDWebImageLowPriority = 1 << 1,
/**
* This flag disables on-disk caching after the download finished, only cache in memory
开启这个选项,禁用磁盘缓存,值缓存在内存中
*/
SDWebImageCacheMemoryOnly = 1 << 2,
/**
* This flag enables progressive download, the image is displayed progressively during download as a browser would do.
* By default, the image is only displayed once completely downloaded.
此标志启用渐进式下载,图像在下载过程中会像浏览器那样逐步显示。
*/
SDWebImageProgressiveDownload = 1 << 3,
/**
* Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed.
* The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation.
* This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics.
* If a cached image is refreshed, the completion block is called once with the cached image and again with the final image.
如果刷新了缓存的图像,则会使用缓存的图像调用一次完成块,并使用最终图像再次调用完成块。
*
* Use this flag only if you can't make your URLs static with embedded cache busting parameter.
利用http自带的缓存类NSURLCache去管理缓存,当同一个url图片更改的时候可以开启这个,以便实时更新图片
*/
SDWebImageRefreshCached = 1 << 4,
/**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
开启这个选项后,在iOS 4+中,如果应用程序转到后台,请继续下载图像。这是通过在后台请求系统额外的时间来完成的。如果后台任务过期,操作将被取消。
*/
SDWebImageContinueInBackground = 1 << 5,
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES处理存储在NSHTTPCookieStore中的cookies;
*/
SDWebImageHandleCookies = 1 << 6,
/**
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
启用以允许不受信任的SSL证书。用于测试。在生产中小心使用。
*/
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
/**
* By default, images are loaded in the order in which they were queued. This flag moves them to
* the front of the queue.
默认情况下,图像按其排队的顺序加载。此标志将它们移动到队列的前面。
*/
SDWebImageHighPriority = 1 << 8,
/**
* By default, placeholder images are loaded while the image is loading. This flag will delay the loading
* of the placeholder image until after the image has finished loading.
默认情况下,在加载图像时加载占位符图像。此标志将延迟占位符图像的加载,直到图像完成加载。
*/
SDWebImageDelayPlaceholder = 1 << 9,
/**
* We usually don't call transformDownloadedImage delegate method on animated images,
* as most transformation code would mangle it.
* Use this flag to transform them anyway.
我们通常不会在动画图像上调用TransformDownloadeImage委托方法,因为大多数转换代码都会破坏它。无论如何,启用此标志来强制转换它们。
*/
SDWebImageTransformAnimatedImage = 1 << 10,
/**
* By default, image is added to the imageView after download. But in some cases, we want to
* have the hand before setting the image (apply a filter or add it with cross-fade animation for instance)
* Use this flag if you want to manually set the image in the completion when success
默认情况下,图片在下载完成后设置到ImageView上。
但在某些情况下,我们希望在设置图片之前做一些额外的操作(例如,应用过滤器或添加交叉淡入淡出动画),如果希望在成功时手动设置完成时的图片,请使用此标志。
避免自动设置图片、手动设置图片
*/
SDWebImageAvoidAutoSetImage = 1 << 11,
/**
* By default, images are decoded respecting their original size. On iOS, this flag will scale down the
* images to a size compatible with the constrained memory of devices.
* If `SDWebImageProgressiveDownload` flag is set the scale down is deactivated.
默认下载的是原图,开启这个选项后会根据设备受限内存的大小来缩放图片。
如果开启了SDWebImageProgressiveDownload这个选项,则开启这个选项是失效的。
*/
SDWebImageScaleDownLargeImages = 1 << 12,
/**
* By default, we do not query disk data when the image is cached in memory. This mask can force to query disk data at the same time.
* This flag is recommend to be used with `SDWebImageQueryDiskSync` to ensure the image is loaded in the same runloop.
默认情况下,如果内存中有数据,就不会查询磁盘的数据,开启这个选项强制同时去查询磁盘数据
*/
SDWebImageQueryDataWhenInMemory = 1 << 13,
/**
* By default, we query the memory cache synchronously, disk cache asynchronously. This mask can force to query disk cache synchronously to ensure that image is loaded in the same runloop.
* This flag can avoid flashing during cell reuse if you disable memory cache or in some other cases.
同步查询磁盘缓存
默认情况下,我们同步查询内存缓存,异步查询磁盘缓存。开启这个选项可以强制同步查询磁盘缓存,以确保图像加载在同一个runloop中。
启用这个选项可以避免在你禁用内存缓存或者在其他一些情况下,在cell复用期间闪烁
*/
SDWebImageQueryDiskSync = 1 << 14,
/**
* By default, when the cache missed, the image is download from the network. This flag can prevent network to load from cache only.
只从缓存中加载
*/
SDWebImageFromCacheOnly = 1 << 15,
/**
* By default, when you use `SDWebImageTransition` to do some view transition after the image load finished, this transition is only applied for image download from the network. This mask can force to apply view transition for memory and disk cache as well.
默认情况下只会当从网络下载的图片完成后进行view的动画。
开启这个选项当图片从内存或者磁盘中读取图片后也进行view的动画
*/
SDWebImageForceTransition = 1 << 16
};
我们再来看看progressBlock
这个参数,这是图片下载的进度回调,它是一个SDWebImageDownloaderProgressBlock
类,我们看看它的源码:
/**
下载进度回调block
@param receivedSize 已下载大小`
@param expectedSize 预计下载大小
@param targetURL 目标链接
*/
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);
再来看看completedBlock
,这是图片下载完成的回调,它是一个SDExternalCompletionBlock
类,我们看看它的源码:
/**
下载完成回调
@param image 下载完成的图片
@param error 有错误的话,回调错误
@param cacheType 图片来源
@param imageURL 图片链接
*/
typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL);
我们点进去这个加载方法,发现它调用了UIView+WebCache
的一个方法,如下:
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:nil
progress:progressBlock
completed:completedBlock];
}
这个方法注释如下:
/**
利用url结合一些自定义选项给view的子类设置图片
@param url 网络链接
@param placeholder 占位图
@param options SDWebImageOptions自定义选项
@param operationKey 用作操作键的字符串。如果为空,将使用类名
@param setImageBlock 自定义设置图像
@param progressBlock 下载进度回调
@param 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
和setImageBlock
两个参数。operationKey
是用来存储operation
的key
,setImageBlock
是SDSetImageBlock
类型自定义设置图像的回调。这个方法跳进去,里面调用了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 {
return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
}
注释如下:
/**
@param 一些额外的上下文字典。比如你可以搞一个专属的imageManager进来干活。
*/
- (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;
这个方法多了一个context
字典类,它可以添加一些额外的信息,自定义是否添加group
或者自定义imageManager
,后面会有详述。里面的调用代码如下:
- (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 {
// 将外部的setImageBlock转换成内部的SDInternalSetImageBlock,继续往下传
SDInternalSetImageBlock internalSetImageBlock;
if (setImageBlock) {
internalSetImageBlock = ^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
if (setImageBlock) {
setImageBlock(image, imageData);
}
};
}
[self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey internalSetImageBlock:internalSetImageBlock progress:progressBlock completed:completedBlock context:context];
}
上面的代码就做了一个操作,将SDSetImageBlock
类的setImageBlock替换成SDInternalSetImageBlock
类的internalSetImageBlock,然后继续调用sd_internalSetImageWithURL:placeholderImage:options: operationKey:internalSetImageBlock:progress:completed:context
这个方法,具体代码如下:
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
internalSetImageBlock:(nullable SDInternalSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary<NSString *, id> *)context {
// 操作键
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
// 根据操作键取消之前的图片下载
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
dispatch_group_t group = context[SDWebImageInternalSetImageGroupKey];
// 如果没有设置延迟加载的选项 则先调用自定义设置图片block回到
if (!(options & SDWebImageDelayPlaceholder)) {
if (group) {
dispatch_group_enter(group);
}
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
if (url) {
#if SD_UIKIT
// check if activityView is enabled or not
// 判断是否显示activityView,要显示的话则显示
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
#endif
// reset the progress
// 重置sd_imageProgress
// totalUnitCount总单元个数
// completedUnitCount 已完成单元个数
self.sd_imageProgress.totalUnitCount = 0;
self.sd_imageProgress.completedUnitCount = 0;
// 有自定义的manager,取出自定义的manager
SDWebImageManager *manager = [context objectForKey:SDWebImageExternalCustomManagerKey];
if (!manager) {
manager = [SDWebImageManager sharedManager];
}
__weak __typeof(self)wself = self;
// 创建进度回调的block
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);
}
};
/**
将图片下载的任务交给 manager : SDWebImageManager
*/
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
// 延长self视图的声明周期
__strong __typeof (wself) sself = wself;
// 如果self此时已释放,则直接返回
if (!sself) { return; }
#if SD_UIKIT
// 移除加载动画
[sself sd_removeActivityIndicator];
#endif
// if the progress not been updated, mark it to complete state
// 如果进度条没更新,则在下载完成及没错误的时候讲进度条设置为完成状态
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);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// 图片存在且设置了手动设置图片的选项
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
// 图片不存在以及没有设置延迟占位图片的的加载选项
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
// 当图片存在,且图片自动加载的情况
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
// 图片不存在且设置了延迟图片加载 将targetImage设为placeholder
targetImage = placeholder;
targetData = nil;
}
#if SD_UIKIT || SD_MAC
// check whether we should use the image transition
// 在下载完成的情况下,设置了SDWebImageForceTransition强制动画或者cacheType为SDImageCacheTypeNone,即从网络下载的图片时,需要加载视图动画
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
transition = sself.sd_imageTransition;
}
#endif
dispatch_main_async_safe(^{
if (group) {
dispatch_group_enter(group);
}
#if SD_UIKIT || SD_MAC
// 通过自定义设置图片的block回调图片
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
// 当有group且FLAnimatedImage加载动画的时候需用dispatch_group_notify调用callCompletedBlockClojure
// 其他情况直接调用callCompletedBlockClojure
if (group) {
// compatible code for FLAnimatedImage, because we assume completedBlock called after image was set. This will be removed in 5.x
// 用来适配FLAnimatedImage加载动画
BOOL shouldUseGroup = [objc_getAssociatedObject(group, &SDWebImageInternalSetImageGroupKey) boolValue];
if (shouldUseGroup) {
dispatch_group_notify(group, dispatch_get_main_queue(), callCompletedBlockClojure);
} else {
callCompletedBlockClojure();
}
} else {
callCompletedBlockClojure();
}
});
}];
// 将操作添加到操作字典中
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
// url为空的情况下
dispatch_main_async_safe(^{
#if SD_UIKIT
// 停掉加载动画
[self sd_removeActivityIndicator];
#endif
// 通过completedBlock回调错误
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
大致的流程如下图:

具体判定是否自动设置图片的流程图如下:

未完待续,下一篇将讲述SDWebImageManager的图片加载流程。