SDWebImage的主要功能及相关知识点
SDWebImage是一个流行的第三方库,用于在iOS和macOS应用程序中异步下载和缓存图像。它提供了一种简单而强大的方式来处理网络图像加载和缓存,具有以下主要功能:
- 异步下载:SDWebImage使用多线程机制,允许在后台异步下载图像,以避免阻塞应用程序的用户界面。
- 图像缓存:它具有内存缓存和磁盘缓存机制,可以自动将下载的图像保存在内存和磁盘中。这样,在后续的加载中,它可以快速从缓存中获取图像,而不必再次下载。
- 占位图和渐进式加载:SDWebImage支持在图像下载期间显示占位图,以及渐进式加载图像,使用户可以逐步看到图像的加载进度。
- 缓存清理:SDWebImage还提供了清理缓存的选项,可以根据需要手动清理过期或不再需要的缓存。
工具类及其功能
- NSData+ImageContentType 通过Image data判断当前图片的格式
- SDImageCache 缓存 定义了 Disk 和 memory二级缓存(NSCache)负责管理cache 单例
- SDWebImageCompat 保证不同平台/版本/屏幕等兼容性的宏定义和内联 图片缩放
- SDWebImageDecoder 图片解压缩,内部只有一个接口
- SDWebImageDownloader 异步图片下载管理,管理下载队列,管理operation 管理网络请求 处理结果和异常 单例
存放网络请求回调的block 自己理解的数据结构大概是
// 结构{“url”:[{“progress”:“progressBlock”},{“complete”:“completeBlock”}]} - SDWebImageDownloaderOperation 实现了异步下载图片的NSOperation,网络请求给予NSURLSession 代理下载
自定义的Operation任务对象,需要手动实现start cancel等方法 - SDWebImageManager 核心管理类 主要对缓存管理 + 下载管理进行了封装 主要接口downloadImageWithURL单利
- SDWebImageOperation operation协议 只定义了cancel operation这一接口 上面的downloaderOperation的代理
- SDWebImagePrefetcher 低优先级情况下预先下载图片,对SDWebImageViewManager进行简单封装 很少用
- MKAnnotationView+WebCache – 为MKAnnotationView异步加载图片
- UIButton+WebCache 为UIButton异步加载图片
- UIImage+GIF 将Image data转换成指定格式图片
- UIImage+MultiFormat 将image data转换成指定格式图片
- UIImageView+HighlightedWebCache 为UIImageView异步加载图片
- UIImageView+WebCache 为UIImageView异步加载图片
- UIView+WebCacheOperation 保存当前MKAnnotationView / UIButton / UIImageView异步下载图片的operations
下载流程
基本使用流程
实现流程
- SDWebImage首先会检查所请求的图片是否存在缓存中(包括内存缓存和磁盘缓存)。如果图片在缓存中找到,将立即从缓存中加载,以提供更快的访问速度。
- 如果在缓存中未找到图片,则SDWebImage会启动一个异步下载任务,从提供的URL下载图片。它会在后台处理网络请求和图片下载,而不会阻塞用户界面。
- 在图片下载期间,SDWebImage可以显示指定的占位图像(如果提供了)在UIImageView中。
- 图片下载完成后,SDWebImage会将其加载到UIImageView中,并自动处理缓存,以便在将来的请求中能够快速获取图片。
源码解析
我们根据调用流程一步一步来。
调用1
调用UIImageView+WebCache中的sd_setImageWithURL系列方法:
- (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 {
[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);
}
}];
}
调用2
上面的全能方法,实际上调用了UIView+WebCache类的一个方法:
- (nullable id<SDWebImageOperation>)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;
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
self.sd_imageURL = url;
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];
}
BOOL shouldUseWeakCache = NO;
if ([manager.imageCache isKindOfClass:SDImageCache.class]) {
shouldUseWeakCache = ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache;
}
if (!(options & SDWebImageDelayPlaceholder)) {
if (shouldUseWeakCache) {
NSString *key = [manager cacheKeyForURL:url context:context];
// call memory cache to trigger weak cache sync logic, ignore the return value and go on normal query
// this unfortunately will cause twice memory cache query, but it's fast enough
// in the future the weak cache feature may be re-design or removed
[((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
}
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
id <SDWebImageOperation> operation = nil;
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
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);
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 callCompletedBlockClosure = ^{
if (!self) {
return; }
if (!shouldNotSetImage) {
[self sd_setNeedsLayout];
}
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, data, error, cacheType, finished, 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(callCompletedBlockClosure);
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;
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) {
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
callCompletedBlockClosure();
});
}];
[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);
}
});
}
return operation;
}
首先是方法名:
其总共有五个参数,URL就是我们需要下载的在线图片链接,placeholder(占位符)Image其是UIImage类型,而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,
/**
* 处理存储在NSHTTPCookieStore中的cookie
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
SDWebImageHandleCookies = 1 << 5,
/**
* 启用允许不受信任的SSL证书。
* 用于测试目的。在生产中请谨慎使用。
*/
SDWebImageAllowInvalidSSLCertificates = 1 << 6,
/**
* 默认情况下,图像按照它们排队的顺序加载。这个标志把他们推在队伍的前面。
*/
SDWebImageHighPriority = 1 << 7,
/**
* 默认情况下,在加载图像时加载占位符图像。此标志将延迟加载占位符图像,直到图像完成加载。
* @注:这用于将占位符视为**错误占位符**,而不是默认的**加载占位符**。如果图像加载被取消或出现错误,占位符将始终被设置。
* 因此,如果你想**错误占位符**和**加载占位符**存在,使用' SDWebImageAvoidAutoSetImage '手动设置两个占位符和最终加载的图像由你的手取决于加载结果。
*/
SDWebImageDelayPlaceholder = 1 << 8,
/**
* 我们通常不会在动画图像上应用变换,因为大多数Transform无法管理动画图像。
* 无论如何的药变换,使用此标志来转换它们。
*/
SDWebImageTransformAnimatedImage = 1 << 9,
/**
* 默认情况下,图片下载后会添加到imageView中。但在某些情况下,我们想要
* 在设置图像之前先设置一下(例如应用滤镜或添加交叉渐变动画)
* 使用此标志,如果你想手动设置图像在完成时成功
*/
SDWebImageAvoidAutoSetImage = 1 << 10,
/**
* 默认情况下,根据图像的原始大小对其进行解码。
* 此标志将缩小图像到与设备受限内存兼容的大小。
* 要控制内存限制,请检查' SDImageCoderHelper.defaultScaleDownLimitBytes ' (iOS上默认为60MB)
* 这将实际转化为使用上下文选项'。imageThumbnailPixelSize '从v5.5.0(在iOS上默认为(3966,3966))。以前没有。
* 从v5.5.0开始,这个标志也会影响渐进式和动画图像。以前没有。
如果你需要细节控件,最好使用上下文选项' imageThumbnailPixelSize '和' imagePreserveAspectRatio '代替。
*/
SDWebImageScaleDownLargeImages = 1 << 11,
/**
* 默认情况下,当图像已经缓存在内存中时,我们不会查询图像数据。此掩码可以强制同时查询图像数据。然而,这个查询是异步的,除非你指定' SDWebImageQueryMemoryDataSync '
*/
SDWebImageQueryMemoryData = 1 << 12,
/**
* 默认情况下,当您只指定' SDWebImageQueryMemoryData '时,我们将异步查询内存图像数据。并结合此掩码同步查询内存图像数据。
* @note不建议同步查询数据,除非你想确保在同一个运行循环中加载图像,以避免在单元重用期间闪烁。
*/
SDWebImageQueryMemoryDataSync = 1