写在前面
SDWebImage是一个强大的图片下载库,提供的主要功能有:图片异步下载,图片缓存,图片解码以及其他确保程序健壮性的功能。其Github地址戳这里。
SDWebImage Class Diagram UML类图
SDWebImage官方提供了这个开源库的类图。
为了方便阅读在上面标注了不同箭头的关系。框架下各个模块之间的关系都展示的很清楚了,就不再一一解释。举个例子,通常使用到的
UIImageView
的
WebCache
分类下
sd_setImageWithURL()
方法依赖于
UIView
的
WebCache
分类下的
sd_internalSetImageWithURL()
方法的实现。而
UIView
的
WebCache
分类又依赖于
SDWebImageManager
模块。第一篇文章则是从这个角度入手分析SDWebImage的下载流程。
SDWebImage Sequence Diagram 流程图
官方提供的流程图如下:
清晰明了。
源码分析
SDWebImageManager
SDWebImageManager是封装好的一个单例,通过
+ (nonnull instancetype)sharedManager;
方法获取。
SDWebImageManager是用于支撑WebCache
分类(如UIImageView+WebCache
)实现的一个类,并且连接了SDWebImageDownloader
异步下载器和SDImageCache
缓存模块。
因此,在SDWebImageManager中封装了以下几个重要属性:
//代理
@property (weak, nonatomic, nullable) id delegate;
//缓存类
@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache;
//异步下载器
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader;
代理对象中有两个重要方法(但是是@optional的)在下面的代码中将会使用到:
/**
* Controls which image should be downloaded when the image is not found in the cache.
*
* @param imageManager The current `SDWebImageManager`
* @param imageURL The url of the image to be downloaded
*
* @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
*/
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;
/**
* Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
* NOTE: This method is called from a global queue in order to not to block the main thread.
*
* @param imageManager The current `SDWebImageManager`
* @param image The image to transform
* @param imageURL The url of the image to transform
*
* @return The transformed image object.
*/
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;
- 第一个方法用于询问代理在查找不到缓存的情况下是否需要根据url下载图片,默认返回YES。如果返回NO,即使缓存未命中,也不执行下载操作。
- 第二个方法用于询问代理是否需要对下载的图像进行transform操作,然后缓存transform之后的图片。如果代理实现了这个方法,则需要返回一张图片。
WebCache
分类中的sd_setImageWithURL
最终都会调用SDWebImageManager
类中的
- (nullable id )loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options :(nullable SDWebImageDownloaderProgressBlock) progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock;
进行图片的请求。
代码注释中有对该方法中传入的几个参数做出的说明,其中需要注意传入的SDInternalCompletionBlock
参数的作用。
/**
* Downloads the image at the given URL if not present in cache or return the cached version otherwise.
*
* @param url The URL to the image
* @param options A mask to specify options to use for this request
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed.
*
* This parameter is required.
*
* This block has no return value and takes the requested UIImage as first parameter and the NSData representation as second parameter.
* In case of error the image parameter is nil and the third parameter may contain an NSError.
*
* The forth parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache
* or from the memory cache or from the network.
*
* The fifth parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is
* downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the
* block is called a last time with the full image and the last parameter set to YES.
*
* The last parameter is the original image URL
*
* @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation
*/
SDWebImageOptions
是一个枚举类型,里面存放了一些用户可以自定义的图片下载/缓存选项,在代码中有用到的话再专门解释其含义。
loadImageWithURL()方法
- (nullable id )loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options :(nullable SDWebImageDownloaderProgressBlock) progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock{
// 1. 判断传入的url合法性
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// 将url设置为nil继续执行后续操作,防止程序崩溃
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
//初始化operation
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
//2. 判断url是否在failedURLs中
if (url) {
@synchronized (self.failedURLs) {// 加了同步锁,保证集合类使用的线程安全
isFailedUrl = [self.failedURLs containsObject:url];
}
}
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
return operation;
}
//将operation添加到数组中,任务完成后将被移除(后面会提到)
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//3. 根据url获取key
NSString *key = [self cacheKeyForURL:url];
//4. 请求缓存
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
if (operation.isCancelled) {
//4.1 操作已取消
[self safelyRemoveOperationFromRunning:operation];
return;
}
//需要更新已在缓存中的图片 || 未获取到缓存图片
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//4.2 options 中的 SDWebImageRefreshCached位为1 || 未获取到缓存图片
//用于处理来自同一个url的图片,但是服务器中的图片已经更新,因此需要强制下载并更新缓存图片。
if (cachedImage && options & SDWebImageRefreshCached) {
//缓存中已存在旧图片,执行回调通知。
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
//继续执行下载任务
SDWebImageDownloaderOptions downloaderOptions = 0;
//省略初始化downloaderOptions代码
if (cachedImage && options & SDWebImageRefreshCached) {
//更新downloaderOptions
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
//使用更新的downloaderOptions开启下载图片任务
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// 如果任务被取消,什么都不做
} else if (error) {
//error handling 错误处理
/*执行回调将error传出并将url放入failedURLs数组中*/
//代码省略
}
else {
if ((options & SDWebImageRetryFailed)) {
/*options的SDWebImageRetryFailed位为1*/
/*默认情况下当url无法下载时,会添加到failedURLs数组中防止反复尝试通过该url下载图片,而如果该位为1,则该机制失效。*/
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//是否需要存储在磁盘上
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
//图片刷新命中NSURLCache的情况 && downloadedImage为空
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
//未命中NSURLCache且downloadedImage不为空
//options的SDWebImageTransformAnimatedImage位为1情况
//此时说明图片需要执行transform操作
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
//此时调用代理方法获取transformed image
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
// pass nil if the image was transformed, so we can recalculate the data from the image
//缓存图片
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
//执行回调
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
if (downloadedImage && finished) {
//一般情况:即以上出现的options位为0且获取到下载的图片
//根据cacheOnDisk缓存downloadedImage
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
//执行回调
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
//通过原子操作移除下载
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
//给cancelBlock辅助,注意这段代码不是在这里执行的
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
}else if (cachedImage) {
//4.3 请求到缓存图片 && !SDWebImageRefreshCached
//直接回调,移除操作
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
// 4.4 图片没有在缓存中 && 代理没有允许下载操作
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
方法内部的注释详细描述了SDWebImageManager的loadImageWithURL
工作流程。概括起来其实很简单:首先判断缓存中是否能获取到图片,如果没获取到则使用异步下载器下载,然后使用缓存类缓存,执行回调返回图片。如果缓存中能够获取到,执行回调返回缓存中的图片。
下一篇文章将着重分析SDWebImage的缓存实现。