[iOS开发]SDWebImage源码学习
Posted Billy Miracle
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[iOS开发]SDWebImage源码学习相关的知识,希望对你有一定的参考价值。
SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能。它具有以下功能:
- 一个异步的图片加载器。
- 一个异步的内存+磁盘图片缓存
- 支持GIF、WebP图片
- 后台图片解压缩处理
- 确保同一个URL的图片不被多次下载
- 确保非法的URL不会被反复加载
- 确保下载及缓存时,主线程不被阻塞。
大致流程如下:
调用
接下来一点点看,我们一般都会使用方法加载图片:
//UIImage+WebCache.h
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
在UIImage+WebCache.h
文件里,我们还可以找到常见的:
- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
completed:(nullable SDExternalCompletionBlock)completedBlock;
等常用方法。
对于SDExternalCompletionBlock
,我们可以在SDWebImageManager.h
中找到定义:
typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL);
查看UIImage+WebCache.m
文件,可以看到这些方法的实现代码:
- (void)sd_setImageWithURL:(nullable NSURL *)url
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
- (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
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context
[self sd_setImageWithURL:url placeholderImage:placeholder options:options context:context progress:nil completed:nil];
- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock
[self sd_setImageWithURL:url placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock];
它们最终都调用了
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
方法。查看它的代码:
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
context:context
setImageBlock:nil
progress:progressBlock
completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL)
if (completedBlock)
completedBlock(image, error, cacheType, imageURL);
];
发现它调用了
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
方法。其简要流程为:
- 将
SDWebImageContext
复制并转换为immutable
,获取其中的validOperationKey
值作为校验id
,默认值为当前view
的类名; - 执行
sd_cancelImageLoadOperationWithKey
取消上一次任务,保证没有当前正在进行的异步下载操作, 不会与即将进行的操作发生冲突; - 设置占位图;
- 初始化
SDWebImageManager
、SDImageLoaderProgressBlock
, 重置NSProgress
、SDWebImageIndicator
; - 开启下载
loadImageWithURL:
并将返回的SDWebImageOperation
存入sd_operationDictionary
,key
为validOperationKey
; - 取到图片后,调用
sd_setImage:
同时为新的image
添加Transition
过渡动画; - 动画结束后停止
indicator
。
在UIView+Cache.h
文件中可以找到它的实现:
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock
if (context)
// copy to avoid mutable object
// 复制以避免可变对象
context = [context copy];
else
context = [NSDictionary dictionary];
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
if (!validOperationKey)
// pass through the operation key to downstream, which can used for tracing operation or image view class
// 通过操作键传递到下游,可用于跟踪操作或图像视图类
validOperationKey = NSStringFromClass([self class]);
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
context = [mutableContext copy];
self.sd_latestOperationKey = validOperationKey;
//1.取消先前的下载任务
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
self.sd_imageURL = url;
if (!(options & SDWebImageDelayPlaceholder))
dispatch_main_async_safe(^
//设置占位图
// 作为图片下载完成之前的替代图片。
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
);
if (url)
// reset the progress
// 重置进度
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
// 获取图像加载进度
if (imageProgress)
imageProgress.totalUnitCount = 0;
imageProgress.completedUnitCount = 0;
#if SD_UIKIT || SD_MAC
// check and start image indicator
// 是否显示进度条
[self sd_startImageIndicator];
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
// 获取图像管理者对象
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
// 如果自定义了图像管理者对象就用自定义的,否则就用单例对象
if (!manager)
manager = [SDWebImageManager sharedManager];
else
// remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
// 删除此管理器以避免保留周期(管理器 -> 加载程序 -> 操作 -> 上下文 -> 管理器)
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextCustomManager] = nil;
context = [mutableContext copy];
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL)
if (imageProgress)
imageProgress.totalUnitCount = expectedSize;
imageProgress.completedUnitCount = receivedSize;
#if SD_UIKIT || SD_MAC
if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)])
double progress = 0;
if (expectedSize != 0)
progress = (double)receivedSize / expectedSize;
progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
dispatch_async(dispatch_get_main_queue(), ^
[imageIndicator updateIndicatorProgress:progress];
);
#endif
if (progressBlock)
progressBlock(receivedSize, expectedSize, targetURL);
;
@weakify(self);
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL)
@strongify(self);
if (!self) return;
// if the progress not been updated, mark it to complete state
// 如果进度未更新,请将其标记为完成状态
if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0)
imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
#if SD_UIKIT || SD_MAC
// check and stop image indicator
// 检查和停止图像指示器
if (finished)
[self sd_stopImageIndicator];
#endif
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^
//self是否被释放
if (!self) return;
if (!shouldNotSetImage)
[self sd_setNeedsLayout];
//不要自动设置图片,调用Block传入UIImage对象
if (completedBlock && shouldCallCompletedBlock)
completedBlock(image, data, error, cacheType, finished, url);
;
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// 我们得到了一个图像,但SDWebImageAvoidAutoSetImage标志被设置了
// OR 或
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
// 我们没有图像,也没有设置SDWebImageDelayPlaceholder
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
// 我们得到了一个图像,但SDWebImageAvoidAutoSetImage没有设置
targetImage = image;
targetData = data;
else if (options & SDWebImageDelayPlaceholder)
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
// 我们没有图像,并且设置了SDWebImageDelayPlaceholder标志
targetImage = placeholder;
targetData = nil;
#if SD_UIKIT || SD_MAC
// check whether we should use the image transition
// 检查我们是否应该使用图像过渡
SDWebImageTransition *transition = nil;
BOOL shouldUseTransition = NO;
if (options & SDWebImageForceTransition)
// Always
shouldUseTransition = YES;
else if (cacheType == SDImageCacheTypeNone)
// From network
shouldUseTransition = YES;
else
// From disk (and, user don't use sync query)
if (cacheType == SDImageCacheTypeMemory)
shouldUseTransition = NO;
else if (cacheType == SDImageCacheTypeDisk)
// SDWebImageQueryMemoryDataSync:
// 默认情况下,当您仅指定“SDWebImageQueryMemoryData”时,我们会异步查询内存映像数据。
// 将此掩码也组合在一起,以同步查询内存图像数据
// 不建议同步查询数据,除非您要确保在同一 runloop 中加载映像以避免在单元重用期间闪烁。
// SDWebImageQueryDiskDataSync:
// 默认情况下,当内存缓存未命中时,我们会异步查询磁盘缓存。此掩码可以强制同步查询磁盘缓存(当内存缓存未命中时)。
// 这两个选项打开则NO。
if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync)
shouldUseTransition = NO;
else
shouldUseTransition = YES;
else
// Not valid cache type, fallback
shouldUseTransition = NO;
if (finished && shouldUseTransition)
transition = self.sd_imageTransition;
#endif
dispatch_main_async_safe(^
#if SD_UIKIT || SD_MAC
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
callCompletedBlockClojure();
);
];
// 将生成的加载操作赋给UIView的自定义属性,并赋值
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
else
#if SD_UIKIT || SD_MAC
[self sd_stopImageIndicator];
#endif
dispatch_main_async_safe(^
if (completedBlock)
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@NSLocalizedDescriptionKey : @"Image url is nil"];
completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
);
参数列表中,有一项SDWebImageOptions,在SDWebImageDefine.h
里面,定义了SDWebImageOptions
,其是一种暴露在外的可供使用者使用的选择方法。
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions)
// 默认情况下,当URL下载失败时,URL会被列入黑名单,导致库不会再去重试,该标记用于禁用黑名单
SDWebImageRetryFailed = 1 << 0,
// 默认情况下,图片下载开始于UI交互,该标记禁用这一特性,这样下载延迟到UIScrollView减速时
SDWebImageLowPriority = 1 << 1,
// 该标记启用渐进式下载,图片在下载过程中是渐渐显示的,如同浏览器一下。
// 默认情况下,图像在下载完成后一次性显示
SDWebImageProgressiveLoad = 1 << 2,
// 即使缓存了映像,也要遵循 HTTP 响应缓存控件,并在需要时从远程位置刷新映像。
// 磁盘缓存将由 NSURLCache 而不是 SDWebImage 处理,从而导致性能略有下降。
// 此选项有助于处理同一请求URL后面的图像变化,例如Facebook图形API个人资料图片。
// 如果刷新缓存的图像,则对缓存的图像调用一次完成块,然后对最终图像调用一次完成块。
// 仅当无法使用嵌入式缓存破坏参数使 URL 保持静态时,才使用此标志。
SDWebImageRefreshCached = 1 << 3,
// 在ios 4+系统中,当程序进入后台后继续下载图片。这将要求系统给予额外的时间让请求完成
// 如果后台任务超时,则操作被取消
SDWebImageContinueInBackground = 1 << 4,
// 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;来处理存储在NSHTTPCookieStore中的cookie
SDWebImageHandleCookies = 1 << 5,
// 允许不受信任的SSL认证
// 用于测试目的。在生产中谨慎使用。
SDWebImageAllowInvalidSSLCertificates = 1 << 6以上是关于[iOS开发]SDWebImage源码学习的主要内容,如果未能解决你的问题,请参考以下文章
SDWebImage源码阅读SDImageCacheConfig/SDImageCache(下)
SDWebImage源码阅读NSImage+WebCache