文章目录
- 什么是SDWebImage?
- sd_setImageWithURL调用关系
- 步骤一
- 步骤三
- 步骤四
- 步骤五
- 步骤六
- 下载步骤
- UIImageView+ WebCache
- UIView+ WebCache
- 第一块:
- 第二块:
- 第三块:
- 第四块:
- 第五块
- 第六块
- 第七块
- 第八块
- 总结
- 相关类名与功能描述
- 缓冲
- 内存缓冲
- 磁盘缓冲
- 清理缓冲的策略
- 相关问题
什么是SDWebImage?
SDWebImage是iOS开发中被广泛使用的第三方开源库,它提供了图片从加载、解析、缓存、清理等一系列功能。
平时我们引进这个第三方库,最主要的就是使用sd_setImageWithURL
方法。下面讲讲这个方法的调用
sd_setImageWithURL调用关系
通过上图,可以知道SDWebImage加载的过程是首先从缓存中加载数据,缓存加载是优先从内存缓存中加载,然后才是磁盘加载。如果没有缓存,才从网络上加载。网络成功加载图片以后,存入本地缓存。
步骤一
根据自己需要选择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
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context;
- (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 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;
而这个方法则是调用了UIView+WebCache类中的方法。
步骤三
上面UIImageView+WebCache调用的就是UIView+WebCache类中的
- (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;
在这个方法里,创建了一个SDWebImageManager
对象,然后去调用其类的loadImageWithURL:()
。
所有的UIButton、UIImageView都回调用这个分类的方法来完成图片加载的处理。同时通过
UIView+WebCacheOperation
分类来管理请求的取消和记录工作。所有UIView及其子类的分类都是用这个类的来实现图片的加载,这个类也是主要实现了这个方法。
步骤四
上面的方法会调用SDWebImageManager
类的:
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock;
在 SDWebImageManager
对象的上述方法里,首先会查询在缓存中有没有这个图片,然后根据各种 option 判断决定是否要从网络端下载。而查询缓存中有没有是通过调用 SDImageCache
对象的实例方法来实现的。
步骤五
上面的方法会调用SDImageCache
类的下面方法从图像缓存中查询给定密钥的缓存图像,如果映像缓存在内存中,则同步调用完成;否则异步调用完成。
- (nullable id<SDWebImageOperation>)queryImageForKey:(nullable NSString *)key
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
cacheType:(SDImageCacheType)cacheType
completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock;
SDImageCache
这个类是专门负责缓存相关的问题的,包括查询缓存和将图片进行缓存。SDImageCache
使用了一个 NSCache
对象来进行内存缓存,磁盘缓存则是把图片数据存放在应用沙盒的Caches这个文件夹下。
这里的查询缓存的步骤为:首先查询内存缓存,内存缓存查询完了以后再判断是否需要查询磁盘缓存。如果查询内存缓存已经有了结果并且没有设置一定要查询磁盘缓存,那么就不查询磁盘缓存,否则就要查询磁盘缓存。内存缓存没有查询到图片,并且磁盘缓存查询到了图片,那么就要把这个内容缓存到内存缓存中,返回缓存查询的结果;如果磁盘缓冲也没有查询到图片,就调用下一个类去下载图片。
步骤六
下载未缓冲过的图片时,使用类SDWebImageDownloader
下面这个方法:
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
下载步骤
整个步骤就是下面这样:
UIImageView+ WebCache
这个分类在上面介绍调用关系时已经说过了,里面的方法都是在为程序员的不同需求提供对外的接口方法,最后都是调用
- (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;
这个方法,没什么说的。
UIView+ WebCache
这个类分析一下- (void)sd_internalSetImageWithURL:()方法:
- (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;
//下面这行代码是保证没有当前正在进行的异步下载操作, 使它不会与即将进行的操作发生冲突
[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];
});
}
// 判断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
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 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);
}
});
}
}
一行一行分析一下:
首先是方法名:
其总共有五个参数,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 graph api配置文件图片。
*如果刷新了缓存的图像,则使用缓存的图像调用一次完成块,然后使用最终图像调用一次完成块。
*
*只有当不能使用嵌入式缓存破坏参数使URL保持静态时,才使用此标志。
*/
SDWebImageRefreshCached = 1 << 3,
/**
* 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.
*/
SDWebImageContinueInBackground = 1 << 4,
/**
*通过设置
*NSMutableURLRequest。HTTPShouldHandleCookies=是;
*/
SDWebImageHandleCookies = 1 << 5,
/**
*启用以允许不受信任的SSL证书。
*用于测试目的。在生产中小心使用。
*/
SDWebImageAllowInvalidSSLCertificates = 1 << 6,
/**
*默认情况下,图像按其排队顺序加载。此标志将它们移动到
*排在队伍前面。
*/
SDWebImageHighPriority = 1 << 7,
/**
*默认情况下,在加载图像时加载占位符图像。此标志将延迟加载
*直到图像加载完毕。
*/
SDWebImageDelayPlaceholder = 1 << 8,
/**
*我们通常不会对动画图像应用变换,因为大多数变形金刚无法管理动画图像。
*无论如何都要使用此标志来变换它们。
*/
SDWebImageTransformAnimatedImage = 1 << 9,
/**
*默认情况下,图像在下载后添加到imageView。但在某些情况下,我们希望
*在设置图像之前先用手(例如,应用过滤器或添加交叉淡入淡出动画)
*如果要在成功时手动设置完成中的图像,请使用此标志
*/
SDWebImageAvoidAutoSetImage = 1 << 10,
/**
*默认情况下,图像将根据其原始大小进行解码。
*此标志将图像缩小到与设备受限内存兼容的大小。
*要控制内存字节的限制,请选中“SDImageCoderHelper”。defaultScaleDownLimitBytes`(在iOS上默认为60MB)
*这实际上将转换为使用上下文选项“”。imageThumbnailPixelSize`来自v5。5.0(在iOS上默认为(39663966)。以前没有。
*以及v5和动画效果中的标志。5.0. 以前没有。
*@note如果你需要细节控件,最好使用上下文选项“imageThumbnailPixelSize”和“ImagePreserveApectRatio”。
*/
SDWebImageScaleDownLargeImages = 1 << 11,
/**
*默认情况下,当图像已经缓存在内存中时,我们不会查询图像数据。这个掩码可以强制同时查询图像数据。但是,除非指定'SDWebImageQueryMemoryDataSync',否则此查询是异步的`
*/
SDWebImageQueryMemoryData = 1 << 12,
/**
*默认情况下,当您只指定'SDWebImageQueryMemoryData'时,我们会异步查询内存图像数据。并结合该掩码同步查询内存图像数据。
*@note不建议同步查询数据,除非您希望确保图像加载在同一个运行循环中,以避免在单元重用期间闪烁。
*/
SDWebImageQueryMemoryDataSync = 1 << 13,
/**
*默认情况下,当内存缓存丢失时,我们异步查询磁盘缓存。此掩码可以强制同步查询磁盘缓存(当内存缓存未命中时)。
*@note这3个查询选项可以组合在一起。有关这些掩码组合的完整列表,请参