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 << 13,
/**
* 默认情况下,当内存缓存丢失时,我们异步查询磁盘缓存。此掩码可以强制同步查询磁盘缓存(当内存缓存丢失时)。
* @注这3个查询选项可以组合在一起。有关这些掩码组合的完整列表,请参阅wiki页面。
* @note不建议同步查询数据,除非你想确保在同一个运行循环中加载图像,以避免在单元重用期间闪烁。
*/
SDWebImageQueryDiskDataSync = 1 << 14,
/**
* 默认情况下,当缓存丢失时,将从加载器加载图像。这个标志可以防止只从缓存加载。
*/
SDWebImageFromCacheOnly = 1 << 15,
/**
* 默认情况下,我们在从加载器加载图像之前查询缓存。这个标志可以防止只从加载器加载。
*/
SDWebImageFromLoaderOnly = 1 << 16,
/**
* 默认情况下,当你使用' SDWebImageTransition '在图片加载完成后做一些视图转换时,这个转换只适用于来自管理器的回调是异步的(来自网络,或磁盘缓存查询)。
* 这个掩码可以强制在任何情况下应用视图转换,如内存缓存查询,或同步磁盘缓存查询。
*/
SDWebImageForceTransition = 1 << 17,
/**
* 默认情况下,我们将在缓存查询时在后台解码图像,然后从网络下载。这有助于提高性能,因为在屏幕上渲染图像时,需要首先对图像进行解码。但这是Core Animation在主队列上发生的。
然而,这个过程也可能增加内存的使用。如果由于内存消耗过多而遇到问题,此标志可以阻止解码图像。
*/
SDWebImageAvoidDecodeImage = 1 << 18,
/**
* 默认情况下,我们解码动画图像。这个标志可以强制解码第一帧,并产生静态图像。
*/
SDWebImageDecodeFirstFrameOnly = 1 << 19,
/**
* 默认情况下,对于' SDAnimatedImage ',我们在渲染期间解码动画图像帧以减少内存使用。但是,当动画图像被许多imageview共享时,您可以指定将所有帧预加载到内存中以减少CPU使用。
* 这将在后台队列中触发' preloadAllAnimatedImageFrames '(仅限磁盘缓存和下载)。
*/
SDWebImagePreloadAllFrames = 1 << 20,
/**
* 默认情况下,当你使用' SDWebImageContextAnimatedImageClass '上下文选项(如使用' SDAnimatedImageView '设计 使用' SDAnimatedImage '),我们可能仍然使用' UIImage '当内存缓存hit,或图像解码器是不可用的产生一个完全匹配你的自定义类作为后备解决方案。
* 使用此选项,可以确保我们总是回调图像与您提供的类。如果未能产生一个,一个错误代码' SDWebImageErrorBadImageData '将被使用。
* 注意这个选项不兼容' SDWebImageDecodeFirstFrameOnly ',它总是产生一个UIImage/NSImage。
*/
SDWebImageMatchAnimatedImageClass = 1 << 21,
/**
* 默认情况下,当我们从网络加载图像时,图像将被写入缓存(内存和磁盘,由' storeCacheType '上下文选项控制)。
* 这可能是一个异步操作,最终的' SDInternalCompletionBlock '回调不能保证磁盘缓存写入完成,可能导致逻辑错误。(例如,您在完成块中修改了磁盘数据,但是磁盘缓存还没有准备好)
* 如果你需要在完成块中处理磁盘缓存,你应该使用这个选项来确保回调时磁盘缓存已经被写入。
* 注意,如果您在使用自定义缓存序列化器或使用转换器时使用此功能,我们也将等待输出图像数据写入完成。
*/
SDWebImageWaitStoreCache = 1 << 22,
/**
* 我们通常不会在矢量图像上应用变换,因为矢量图像支持动态更改为任何大小,栅格化到固定大小会丢失细节。要修改矢量图像,可以在运行时处理矢量数据(例如修改PDF标记/ SVG元素)。
* 无论如何都要在矢量图片上应用变换,使用此标志来转换它们。
*/
SDWebImageTransformVectorImage = 1 << 23
};
1. context
if (context) {
// copy to avoid mutable object
// 避免可变对象
context = [context copy];
} else {
context = [NSDictionary dictionary];
}
// 利用context获取validOperationKey
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;
对于
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
其源码:
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
if (key) {
// Cancel in progress downloader from queue
// 从队列中取消正在进行的加载
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
id<SDWebImageOperation> operation;
@synchronized (self) {
operation = [operationDictionary objectForKey:key];
}
if (operation) {
if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
[operation cancel];
}
@synchronized (self) {
[operationDictionary removeObjectForKey:key];
}
}
}
}
这段代码在UIView+WebCacheOperation
中,这个类主要负责对下载operation的操作。它使用关联对象针对每个UIKit对象在内存中维护一个字典operationDictionary。可以对不同的key值添加对应的下载operation,也可以在下载操作没有完成的时候根据key取到operation进行取消。
operationDictionary的key一般是类名。如此同一个UIImageView同时调用两次,第一次的下载操作会先被取消,然后将operationDictionary的中的operation对应到第二次的下载操作。
然后我们来看看operationDictionary的源码:
- (SDOperationsDictionary *)sd_operationDictionary {
@synchronized(self) {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, @selector(sd_operationDictionary));
if (operations) {
return operations;
}
operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
objc_setAssociatedObject(self, @selector(sd_operationDictionary), operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
}
通过键值返回一个关联对象。如果operations为空,那么就创建一个对象,与键值绑定。
如果有两个相同的键值进入,得到的关联对象也是一样的。传入key在返回的字典中查找是否已经存在,如果存在则取消所有操作,conformsToProtocol方法如果符合这个协议(协议中声明了取消方法),也调用协议中的取消方法。
好处:
- 因为其是针对的一个UIImageView,取消前一个操作,省时、省流量
- 避免SDWebImage的复用。 也就是避免对一张图片进行重复下载。加载图片完成后, 回调时会先检查任务的Operation还在不在, 不在,则不回调显示, 反之回调显示并移除Operation.
- 当程序中断导致链接失效时,当前的下载还在操作队列中,但是按道理应该是失效状态,我们可以通过先取消当前正在进行的下载来保证操作队列中的链接不存在这种情况。
2. 创建图片管理器
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];
}
如果没有创建过,就使用单例创建;如果创建过,就删除context中的管理器,避免保留循环。
3. 设置占位图
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];
});
}
这段代码主要用于根据特定条件设置占位图像,并在需要时触发弱缓存的同步逻辑。请注意,这段代码可能随着库的更新和重构而发生变化或移除。
4. 判断url
if (url) {
// 重置进度
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (imageProgress) {
imageProgress.totalUnitCount = 0;
imageProgress.completedUnitCount = 0;
}
判断传入的url是否为空,如果不为空,则获取图片加载进度并重置为0。
5. 加载图像指示器
// 代码片段中的条件编译指令#if SD_UIKIT || SD_MAC用于在UIKit或AppKit环境下执行相应的逻辑。
// 在这段代码中,首先调用sd_startImageIndicator方法来启动图片加载指示器。然后获取self.sd_imageIndicator的值,该值表示图片指示器对象。
#if SD_UIKIT || SD_MAC
// check and start image indicator
// 检查并启动图像指示器
[self sd_startImageIndicator];
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
// 定义了一个名为combinedProgressBlock的块变量,它会在图片加载进度更新时被调用。该块变量接收三个参数:receivedSize表示已接收的数据大小,expectedSize表示预期的总数据大小,targetURL表示目标URL。
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
// 在块内部,首先检查imageProgress对象是否存在,并根据已接收和预期大小更新其totalUnitCount和completedUnitCount属性,以便跟踪加载进度。
if (imageProgress) {
imageProgress.totalUnitCount = expectedSize;
imageProgress.completedUnitCount = receivedSize;
}
#if SD_UIKIT || SD_MAC
// 通过条件编译指令检查imageIndicator对象是否响应updateIndicatorProgress:方法。如果是,则计算进度值,并将其限制在0到1之间。然后,使用dispatch_async将更新进度的代码块调度到主队列中,以在主线程上执行更新操作。
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
// 如果存在progressBlock,则调用该块来传递接收大小、预期大小和目标URL。
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
这段代码片段在SDWebImage库中用于处理图片加载过程中的进度更新。
6. 创建SDWebImageOperation
@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; }
// 在加载完成且没有错误的情况下,检查进度是否未被更新。如果是这样,并且进度的totalUnitCount和completedUnitCount都为0,则将其标记为未知进度(SDWebImageProgressUnitCountUnknown)。
if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
调用- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nonnull SDInternalCompletionBlock)completedBlock
方法,创建下载任务。
6.1 看看上面创建下载任务得源码
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
// 检查url的合法性
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
// 检查url是否是NSString类,如果是转换为url
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
// 检查url是否是NSURL,如果不是,将url置为空
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
// 新建SDWebImageCombinedOperation对象,将对象的manager设置为self
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self;
// failedURLs是NSMutableSet<NSURL *>,里面保存了失败过的URL。如果url的地址为空,或者该URL请求失败过且没有设置重试SDWebImageRetryFailed选项,则直接直接调用完成。
BOOL isFailedUrl = NO;
if (url) {
SD_LOCK(_failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
SD_UNLOCK(_failedURLsLock);
}
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];
return operation;
}
// 保存SDWebImageCombinedOperation对象
SD_LOCK(_runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(_runningOperationsLock);
// 预处理选项和上下文参数,以决定管理器的最终结果
SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
// 启动条目以从缓存加载图像
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
return operation;
}
6.2 上方法经过url验证后会开始缓存查找
调用方法:- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// 获取要使用的图像缓存
id<SDImageCache> imageCache;
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache;
}
// 获取查询缓存类型
SDImageCacheType queryCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextQueryCacheType]) {
queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
}
// 检查是否需要查询缓存
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
if (shouldQueryCache) {
NSString *key = [self cacheKeyForURL:url context:context];
@weakify(operation);
// 如果需要进行缓存查询,首先生成查询缓存的键值key,然后使用imageCache对象调用queryImageForKey:options:context:cacheType:completion:方法来进行缓存查询。
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
@strongify(operation);
if (!operation || operation.isCancelled) {
// 图像合并操作被用户取消
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
[self safelyRemoveOperationFromRunning:operation];
return;
} else if (!cachedImage) {
BOOL mayInOriginalCache = context[SDWebImageContextImageTransformer] || context[SDWebImageContextImageThumbnailPixelSize];
// 有机会查询原始缓存,而不是下载,然后应用转换
// 缩略图解码是在SDImageCache的解码部分完成的,它不需要转换的后期处理
if (mayInOriginalCache) {
[self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
return;
}
}
// 继续下载过程
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
}];
} else {
// 继续下载过程
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
}
}
精简一下就是:
BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0;
if (shouldQueryCache) {
// 缓存查找
}else {
// 进行下载操作
}
6.3 查询函数:
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock {
// 检查参数
if (!key) {
return nil;
}
NSArray<id<SDImageCache>> *caches = self.caches;
// 获取缓存数量
NSUInteger count = caches.count;
if (count == 0) {
return nil;
} else if (count == 1) {
return [caches.firstObject queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
switch (self.queryOperationPolicy) {
case SDImageCachesManagerOperationPolicyHighestOnly: {
id<SDImageCache> cache = caches.lastObject;
return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyLowestOnly: {
id<SDImageCache> cache = caches.firstObject;
return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyConcurrent: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self concurrentQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
return operation;
}
break;
case SDImageCachesManagerOperationPolicySerial: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self serialQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
return operation;
}
break;
default:
return nil;
break;
}
}
这个方法根据缓存数量的不同,做不同的处理:
- 缓存数量为0,直接返回空;
- 缓存数量为1,则直接调用第一个缓存对象的
queryImageForKey:options:context:cacheType:completion:
方法,并将参数透传给它。 - 缓存数量大于1,则根据设置的查询操作策略queryOperationPolicy进行不同的处理:
- 如果策略为SDImageCachesManagerOperationPolicyHighestOnly,则选择最后一个缓存对象进行查询。
- 如果策略为SDImageCachesManagerOperationPolicyLowestOnly,则选择第一个缓存对象进行查询。
- 如果策略为SDImageCachesManagerOperationPolicyConcurrent,则创建一个SDImageCachesManagerOperation对象作为查询操作,并逐个遍历缓存对象进行并发查询。
- 如果策略为SDImageCachesManagerOperationPolicySerial,则创建一个SDImageCachesManagerOperation对象作为查询操作,并逐个遍历缓存对象进行串行查询。
6.4 上函数将进一步调用SDImageCache类的同名函数
源码:
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData;
if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync;
if (options & SDWebImageQueryDiskDataSync) cacheOptions |= SDImageCacheQueryDiskDataSync;
if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
if (options & SDWebImageAvoidDecodeImage) cacheOptions |= SDImageCacheAvoidDecodeImage;
if (options & SDWebImageDecodeFirstFrameOnly) cacheOptions |= SDImageCacheDecodeFirstFrameOnly;
if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames;
if (options & SDWebImageMatchAnimatedImageClass) cacheOptions |= SDImageCacheMatchAnimatedImageClass;
return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock];
}
这段代码根据传入的选项options设置相应的缓存选项cacheOptions,然后调用queryCacheOperationForKey:options:context:cacheType:done
:方法执行缓存查询操作,并返回查询操作的对象。
缓存选项cacheOptions包括:
- SDWebImageQueryMemoryData: 查询内存缓存的数据。
- SDWebImageQueryMemoryDataSync: 同步查询内存缓存的数据。
- SDWebImageQueryDiskDataSync: 同步查询磁盘缓存的数据。
- SDWebImageScaleDownLargeImages: 缩小大图片的尺寸。
- SDWebImageAvoidDecodeImage: 避免解码图片。
- SDWebImageDecodeFirstFrameOnly: 只解码动画图片的第一帧。
- SDWebImagePreloadAllFrames: 预加载动画图片的所有帧。
- SDWebImageMatchAnimatedImageClass: 匹配动画图片的类。
6.5 调用真正执行缓存查询操作的函数
源码:
- (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 无效的缓存类型
if (queryCacheType == SDImageCacheTypeNone) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 首先检查内存缓存…
UIImage *image;
if (queryCacheType != SDImageCacheTypeDisk) {
image = [self imageFromMemoryCacheForKey:key];
}
if (image) {
if (options & SDImageCacheDecodeFirstFrameOnly) {
// 确保静态图像
Class animatedImageClass = image.class;
if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
#if SD_MAC
image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
}
} else if (options & SDImageCacheMatchAnimatedImageClass) {
// 检查图像类匹配
Class animatedImageClass = image.class;
Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
image = nil;
}
}
}
BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
// 第二,检查磁盘缓存…
SDImageCacheToken *operation = [[SDImageCacheToken alloc] initWithDoneBlock:doneBlock];
operation.key = key;
// 检查是否需要同步查询磁盘
// 1 内存缓存命中& memoryDataSync
// 2 内存缓存丢失& diskDataSync
BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
(!image && options & SDImageCacheQueryDiskDataSync));
NSData* (^queryDiskDataBlock)(void) = ^NSData* {
@synchronized (operation) {
if (operation.isCancelled) {
return nil;
}
}
return [self diskImageDataBySearchingAllPathsForKey:key];
};
UIImage* (^queryDiskImageBlock)(NSData*) = ^UIImage*(NSData* diskData) {
@synchronized (operation) {
if (operation.isCancelled) {
return nil;
}
}
UIImage *diskImage;
if (image) {
// 图像来自内存缓存,但需要图像数据
diskImage = image;
} else if (diskData) {
BOOL shouldCacheToMomery = YES;
if (context[SDWebImageContextStoreCacheType]) {
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
}
if (context[SDWebImageContextImageThumbnailPixelSize]) {
// 查询生成缩略图的全大小缓存键,不应该写回全大小内存缓存
shouldCacheToMomery = NO;
}
// 只有在内存缓存丢失时才解码图像数据
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
}
return diskImage;
};
// 在ioQueue中查询以保证io安全
if (shouldQueryDiskSync) {
__block NSData* diskData;
__block UIImage* diskImage;
dispatch_sync(self.ioQueue, ^{
diskData = queryDiskDataBlock();
diskImage = queryDiskImageBlock(diskData);
});
if (doneBlock) {
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
}
} else {
dispatch_async(self.ioQueue, ^{
NSData* diskData = queryDiskDataBlock();
UIImage* diskImage = queryDiskImageBlock(diskData);
@synchronized (operation) {
if (operation.isCancelled) {
return;
}
}
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
//从IO队列调度到主队列需要时间,用户可以在调度期间调用cancel
//这个检查是为了避免双重回调(一个是来自' SDImageCacheToken '同步)
@synchronized (operation) {
if (operation.isCancelled) {
return;
}
}
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
});
}
return operation;
}
- 内存缓存查找,使用
imageFromMemoryCacheForKey:
方法
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memoryCache objectForKey:key];
}
这里直接通过key去cache里面查找,如果这里找到了,并且没有强制指明查询磁盘的话直接回调。
BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
- 磁盘缓存查找,使用
setObject:forKey:cost:
方法
上一步如果没有找到的话,就会进如磁盘查找阶段,此时会新建一个NSOperation,执行queryDiskBlock代码块
- 通过key查找磁盘获取diskData
- 如果上一步内存找到image直接赋值,否则解码diskData,并且把磁盘的image写入内存
[self.memoryCache setObject:diskImage forKey:key cost:cost];
6.6 调用下载方法
当所有查找都没有找到就会调用下载方法。
首先在queryImageForKey:options:context: cacheType:completion:
回调block中判断是否需要下载:
@strongify(operation);
if (!operation || operation.isCancelled) {
// 图像合并操作被用户取消
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
[self safelyRemoveOperationFromRunning:operation];
return;
} else if (!cachedImage) {
BOOL mayInOriginalCache = context[SDWebImageContextImageTransformer] || context[SDWebImageContextImageThumbnailPixelSize];
// 有机会查询原始缓存,而不是下载,然后应用转换
// 缩略图解码是在SDImageCache的解码部分完成的,它不需要转换的后期处理
if (mayInOriginalCache) {
[self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
return;
}
如果需要下载,则调用SDWebImageDownloader的callDownloadProcessForOperation。
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
cachedImage:(nullable UIImage *)cachedImage
cachedData:(nullable NSData *)cachedData
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// 标记缓存操作结束
@synchronized (operation) {
operation.cacheOperation = nil;
}
// 抓取要使用的图像加载器
id<SDImageLoader> imageLoader;
if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
imageLoader = context[SDWebImageContextImageLoader];
} else {
imageLoader = self.imageLoader;
}
// 检查我们是否需要从网络下载图像
BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context];
} else {
shouldDownload &= [imageLoader canRequestImageForURL:url];
}
if (shouldDownload) {
if (cachedImage && options & SDWebImageRefreshCached) {
// 如果在缓存中找到图像,但是提供了SDWebImageRefreshCached,则通知缓存的图像
// 并尝试重新下载,以便有机会NSURLCache从服务器刷新它。
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
// 将缓存的图像传递给图像加载器。图像加载器应该检查远程图像是否等于缓存的图像。
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
context = [mutableContext copy];
}
@weakify(operation);
operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
@strongify(operation);
if (!operation || operation.isCancelled) {
// 图像合并操作被用户取消
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url];
} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
// 图像刷新击中NSURLCache缓存,不调用完成块
} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
// 下载操作在发送请求前被用户取消,不阻止失败的URL
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
} else if (error) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];
if (shouldBlockFailedURL) {
SD_LOCK(self->_failedURLsLock);
[self.failedURLs addObject:url];
SD_UNLOCK(self->_failedURLsLock);
}
} else {
if ((options & SDWebImageRetryFailed)) {
SD_LOCK(self->_failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self->_failedURLsLock);
}
// 继续存储缓存进程
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData cacheType:SDImageCacheTypeNone finished:finished completed:completedBlock];
}
if (finished) {
[self safelyRemoveOperationFromRunning:operation];
}
}];
} else if (cachedImage) {
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
// 图像不在缓存中,委托不允许下载
[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}
这段代码根据传入的参数判断是否需要下载图片,并根据情况调用图片加载器进行图片下载操作。
如果需要下载图片,首先判断是否存在缓存的图片cachedImage并且选项包含SDWebImageRefreshCached。如果满足条件,首先通知关于缓存图片的完成块,然后将缓存的图片传递给图片加载器,并在上下文context中添加键值对SDWebImageContextLoaderCachedImage,将缓存的图片传递给图片加载器进行比较。然后,调用图片加载器的requestImageWithURL:options:context:progress:completed:
方法,传入URL地址url、选项options、上下文context、进度块progressBlock和完成块completedBlock,执行图片下载操作。
关于requestImageWithURL:options:context:progress:completed:
方法:
- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
UIImage *cachedImage = context[SDWebImageContextLoaderCachedImage];
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveLoad) downloaderOptions |= SDWebImageDownloaderProgressiveLoad;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
if (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage;
if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly;
if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames;
if (options & SDWebImageMatchAnimatedImageClass) downloaderOptions |= SDWebImageDownloaderMatchAnimatedImageClass;
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
}
这段代码根据上下文context获取缓存的图片cachedImage,然后根据传入的选项options和其他条件设置图片下载器的选项downloaderOptions。
downloaderOptions选项包括:
- SDWebImageLowPriority: 低优先级下载。
- SDWebImageProgressiveLoad: 渐进式加载。
- SDWebImageRefreshCached: 强制刷新缓存。
- SDWebImageContinueInBackground: 在后台继续下载。
- SDWebImageHandleCookies: 处理Cookies。
- SDWebImageAllowInvalidSSLCertificates: 允许无效的SSL证书。
- SDWebImageHighPriority: 高优先级下载。
- SDWebImageScaleDownLargeImages: 缩放下载的大图。
- SDWebImageAvoidDecodeImage: 避免解码图片。
- SDWebImageDecodeFirstFrameOnly: 仅解码第一帧。
- SDWebImagePreloadAllFrames: 预加载所有帧。
- SDWebImageMatchAnimatedImageClass: 匹配动图的类。
如果存在缓存的图片cachedImage并且选项包含SDWebImageRefreshCached,则将选项downloaderOptions中的渐进式加载标志和从NSURLCache读取图片的标志关闭。
最后,调用图片下载器的downloadImageWithURL:options:context:progress:completed:
方法,执行图片下载操作。
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// URL将被用作回调字典的键,因此它不能为nil。如果为nil,则立即调用没有图像或数据的已完成块。
if (url == nil) {
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
completedBlock(nil, nil, error, YES);
}
return nil;
}
SD_LOCK(_operationsLock);
id downloadOperationCancelToken;
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
// 有一种情况,操作可能被标记为完成或取消,但没有从' self. urlooperations '中删除。
if (!operation || operation.isFinished || operation.isCancelled) {
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
if (!operation) {
SD_UNLOCK(_operationsLock);
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
completedBlock(nil, nil, error, YES);
}
return nil;
}
@weakify(self);
operation.completionBlock = ^{
@strongify(self);
if (!self) {
return;
}
SD_LOCK(self->_operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self->_operationsLock);
};
self.URLOperations[url] = operation;
// 在提交到操作队列之前添加处理程序,避免操作在设置处理程序之前完成的竞争情况。
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
// 根据Apple的文档完成所有配置后,才将操作添加到操作队列。
// ' addOperation: '不会同步执行' operation.completionBlock ',因此不会导致死锁。
[self.downloadQueue addOperation:operation];
} else {
// 当我们重用下载操作来附加更多回调时,可能会有线程安全问题,因为回调的getter可能在另一个队列中(解码队列或委托队列)
// 所以我们在这里锁定操作,在' SDWebImageDownloaderOperation '中,我们使用' @synchonzied (self) ',以确保这两个类之间的线程安全。
@synchronized (operation) {
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
if (!operation.isExecuting) {
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
} else {
operation.queuePriority = NSOperationQueuePriorityNormal;
}
}
}
SD_UNLOCK(_operationsLock);
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
这段代码根据传入的参数创建或获取图片下载操作,并进行相应的设置和处理。
- 检查URL地址url是否为nil。如果是nil,立即调用完成块completedBlock,返回一个错误对象,并且没有图片和数据。
- 加锁_operationsLock,并根据URL地址url从self.URLOperations字典中获取下载操作operation。如果下载操作不存在或已完成或已取消,则创建新的下载操作operation,并添加到self.URLOperations字典中。同时,设置操作完成后的回调,包括删除self.URLOperations中的对应条目。将进度块progressBlock和完成块completedBlock添加到操作中,并返回一个取消令牌downloadOperationCancelToken。最后,将操作添加到下载队列downloadQueue中。
- 如果下载操作已存在,则加锁操作operation,添加进度块progressBlock和完成块completedBlock到操作中。如果操作未执行,则根据选项options设置操作的队列优先级。然后解锁_operationsLock。
- 最后,创建一个SDWebImageDownloadToken对象token,并将操作operation、URL地址url、请求对象operation.request和取消令牌downloadOperationCancelToken设置给token。最后返回token。
这段代码创建下载操作operation调用了createDownloaderOperationWithUrl:options:context:
方法,最后我们看一下这个方法。
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context {
NSTimeInterval timeoutInterval = self.config.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// 为了防止潜在的重复缓存(NSURLCache + SDImageCache),如果被告知其他情况,我们禁用图像请求的缓存
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies);
mutableRequest.HTTPShouldUsePipelining = YES;
SD_LOCK(_HTTPHeadersLock);
mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
SD_UNLOCK(_HTTPHeadersLock);
// 上下文选项
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
// 请求修改器
id<SDWebImageDownloaderRequestModifier> requestModifier;
if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
} else {
requestModifier = self.requestModifier;
}
NSURLRequest *request;
if (requestModifier) {
NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]];
// 如果修改后的请求为nil,则提前返回
if (!modifiedRequest) {
return nil;
} else {
request = [modifiedRequest copy];
}
} else {
request = [mutableRequest copy];
}
// 反应修饰符
id<SDWebImageDownloaderResponseModifier> responseModifier;
if ([context valueForKey:SDWebImageContextDownloadResponseModifier]) {
responseModifier = [context valueForKey:SDWebImageContextDownloadResponseModifier];
} else {
responseModifier = self.responseModifier;
}
if (responseModifier) {
mutableContext[SDWebImageContextDownloadResponseModifier] = responseModifier;
}
// 解密
id<SDWebImageDownloaderDecryptor> decryptor;
if ([context valueForKey:SDWebImageContextDownloadDecryptor]) {
decryptor = [context valueForKey:SDWebImageContextDownloadDecryptor];
} else {
decryptor = self.decryptor;
}
if (decryptor) {
mutableContext[SDWebImageContextDownloadDecryptor] = decryptor;
}
context = [mutableContext copy];
// 操作类
Class operationClass = self.config.operationClass;
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
// 自定义操作类
} else {
operationClass = [SDWebImageDownloaderOperation class];
}
NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
if ([operation respondsToSelector:@selector(setCredential:)]) {
if (self.config.urlCredential) {
operation.credential = self.config.urlCredential;
} else if (self.config.username && self.config.password) {
operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession];
}
}
if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {
operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
}
if ([operation respondsToSelector:@selector(setAcceptableStatusCodes:)]) {
operation.acceptableStatusCodes = self.config.acceptableStatusCodes;
}
if ([operation respondsToSelector:@selector(setAcceptableContentTypes:)]) {
operation.acceptableContentTypes = self.config.acceptableContentTypes;
}
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// 系统地模拟后进先出的执行顺序,每个之前的加法操作都可以依赖于新操作
// 这可以保证新操作首先被执行,即使当一些操作完成时,你同时追加新的操作
// 仅使最后添加的操作依赖于新操作不能解决这个问题。参见测试用例#test15DownloaderLIFOExecutionOrder
for (NSOperation *pendingOperation in self.downloadQueue.operations) {
[pendingOperation addDependency:operation];
}
}
return operation;
}
总体而言,这段代码根据传入的URL、选项和上下文创建一个用于下载图片的操作对象。它设置请求的超时时间、缓存策略和其他属性,同时根据配置中的信息设置操作的凭证、队列优先级等。通过上下文中的选项,还可以应用请求和响应的修改器以及解密器。最终,返回创建的操作对象供后续使用。
这段代码是SDWebImage库中图片下载的核心逻辑之一,用于处理下载操作的创建和配置。
然后就使用NSURLSession下载,所以在SDWebImageDownloader有着NSURLSession的代理方法。
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(proposedResponse);
}
}
}
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
[dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(request);
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)]) {
[dataOperation URLSession:session task:task didFinishCollectingMetrics:metrics];
}
}
这些方法都调用了(NSOperation *)operationWithTask:(NSURLSessionTask *)task。
通过task获取对应的SDWebImageDownloaderOperation实例,将具体的操作放到了这个类里面,在这个类里面这里面对需要解码的图片做了解码操作和判断是否图片需要缩放等处理。
6.6 图片缓存
回调到requestImageWithURL:options:context:progress:completed:
函数的完成块,如果下载顺利,调用callStoreCacheProcessForOperation:url:options:context:downloadedImage:downloadedData:cacheType:finished:completed
函数缓存进度。
- (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
downloadedImage:(nullable UIImage *)downloadedImage
downloadedData:(nullable NSData *)downloadedData
cacheType:(SDImageCacheType)cacheType
finished:(BOOL)finished
completed:(nullable SDInternalCompletionBlock)completedBlock {
// 抓取要使用的图像缓存,首先选择独立的原始缓存
id<SDImageCache> imageCache;
if ([context[SDWebImageContextOriginalImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
imageCache = context[SDWebImageContextOriginalImageCache];
} else {
// 如果没有可用的独立缓存,则使用默认缓存
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache;
}
}
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
// 目标映像存储缓存类型
SDImageCacheType storeCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextStoreCacheType]) {
storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
}
// 原始存储映像缓存类型
SDImageCacheType originalStoreCacheType = SDImageCacheTypeDisk;
if (context[SDWebImageContextOriginalStoreCacheType]) {
originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
}
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
transformer = nil;
}
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
// 变压器检查
BOOL shouldTransformImage = downloadedImage && transformer;
shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage));
// 缩略图检查
BOOL shouldThumbnailImage = context[SDWebImageContextImageThumbnailPixelSize] != nil || downloadedImage.sd_decodeOptions[SDImageCoderDecodeThumbnailPixelSize] != nil;
BOOL shouldCacheOriginal = downloadedImage && finished && cacheType == SDImageCacheTypeNone;
// 如果可用,将原始图像存储到缓存中
if (shouldCacheOriginal) {
// 获取原始缓存密钥生成,不需要转换/缩略图
NSString *key = [self originalCacheKeyForURL:url context:context];
// 通常使用存储缓存类型,但如果目标图像被转换,则使用原始存储缓存类型代替
SDImageCacheType targetStoreCacheType = (shouldTransformImage || shouldThumbnailImage) ? originalStoreCacheType : storeCacheType;
UIImage *fullSizeImage = downloadedImage;
if (shouldThumbnailImage) {
// 缩略图解码不保留原始图像
// 这里我们只存储原始数据到磁盘的原始缓存键
// 在' storeTransformCacheProcess '中将缩略图存储到内存中以获取缩略图缓存键
fullSizeImage = nil;
}
if (fullSizeImage && cacheSerializer && (targetStoreCacheType == SDImageCacheTypeDisk || targetStoreCacheType == SDImageCacheTypeAll)) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
NSData *cacheData = [cacheSerializer cacheDataWithImage:fullSizeImage originalData:downloadedData imageURL:url];
[self storeImage:fullSizeImage imageData:cacheData forKey:key imageCache:imageCache cacheType:targetStoreCacheType waitStoreCache:waitStoreCache completion:^{
// 继续转换过程
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
}];
}
});
} else {
[self storeImage:fullSizeImage imageData:downloadedData forKey:key imageCache:imageCache cacheType:targetStoreCacheType waitStoreCache:waitStoreCache completion:^{
// 继续转换过程
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
}];
}
} else {
// 继续转换过程
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:cacheType finished:finished completed:completedBlock];
}
}
这个函数对图像缓存进行一些设置,主要负责缓存的函数是storeImage:imageData:forKey:imageCache:cacheType:waitStoreCache:completion:
。下面我们来看看这个函数:
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)data
forKey:(nullable NSString *)key
imageCache:(nonnull id<SDImageCache>)imageCache
cacheType:(SDImageCacheType)cacheType
waitStoreCache:(BOOL)waitStoreCache
completion:(nullable SDWebImageNoParamsBlock)completion {
// 检查我们是否应该等待存储缓存完成。如果没有,立即回调
[imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{
if (waitStoreCache) {
if (completion) {
completion();
}
}
}];
if (!waitStoreCache) {
if (completion) {
completion();
}
}
}
发现这个函数仅仅只是检查是否应该等待存储缓存完成,并调用了storeImage:imageData:orKey:cacheType:completion:
函数。我们继续往下看:
- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
switch (cacheType) {
case SDImageCacheTypeNone: {
[self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:NO completion:completionBlock];
}
break;
case SDImageCacheTypeMemory: {
[self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:NO completion:completionBlock];
}
break;
case SDImageCacheTypeDisk: {
[self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:YES completion:completionBlock];
}
break;
case SDImageCacheTypeAll: {
[self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:YES completion:completionBlock];
}
break;
default: {
if (completionBlock) {
completionBlock();
}
}
break;
}
}
可以看到,这个函数对缓存方法设置了一些选项,并调用真正的缓存函数storeImage:imageData:forKey:toMemory:toDisk:completion:
。
最后让我们看看这个函数:
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toMemory:(BOOL)toMemory
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if ((!image && !imageData) || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
if (image && toMemory && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = image.sd_memoryCost;
[self.memoryCache setObject:image forKey:key cost:cost];
}
if (!toDisk) {
if (completionBlock) {
completionBlock();
}
return;
}
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) {
// If image is custom animated image class, prefer its original animated data
data = [((id<SDAnimatedImage>)image) animatedImageData];
}
if (!data && image) {
// Check image's associated image format, may return .undefined
SDImageFormat format = image.sd_imageFormat;
if (format == SDImageFormatUndefined) {
// If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
if (image.sd_isAnimated) {
format = SDImageFormatGIF;
} else {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
format = [SDImageCoderHelper CGImageContainsAlpha:image.CGImage] ? SDImageFormatPNG : SDImageFormatJPEG;
}
}
data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
}
[self _storeImageDataToDisk:data forKey:key];
[self _archivedDataWithImage:image forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
可以看到self.memCache直接存入内存
if (image && toMemory && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = image.sd_memoryCost;
[self.memoryCache setObject:image forKey:key cost:cost];
}
如果需要存入磁盘的话,在异步队列self.ioQueue中调用方法写入磁盘,
所有的缓存都有时效和大小的限制,在SDImageCache中有着一些列的清理策略 默认图片的有效缓存期为7天:
- 内存清理
[self.memCache removeAllObjects];
- 磁盘清理
SDWebImage监听了app进入后台的通知,当进入后台时,如果当前缓存量大于等于设置的最大缓存量,那么按时间顺序递归删除一般的内容,直到缓存量满足条件。
7 下载图片
下载图片操作:
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
看看其实现:
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
[operationDictionary setObject:operation forKey:key];
}
}
}
}
所有的操作都是由一个operationDictionary字典维护的,执行新的操作之前,cancel所有的operation。
总结
这个方法涉及的方法很多,简单说一下流程:
- 将 SDWebImageContext 复制并转换为 mutable,获取其中的 validOperationKey 值作为校验 id,默认值为当前 view 的类名;
- 执行 sd_cancelImageLoadOperationWithKey 取消上一次任务,保证没有当前正在进行的异步下载操作, 不会与即将进行的操作发生冲突;
设置占位图。 - 初始化 SDWebImageManager 、SDImageLoaderProgressBlock , 重置 NSProgress、SDWebImageIndicator;
- 开启下载loadImageWithURL: 并将返回的 SDWebImageOperation 存入 sd_operationDictionary,key 为 validOperationKey;
- 取到图片后,调用 sd_setImage: 同时为新的 image 添加 Transition 过渡动画;
动画结束后停止 indicator。
关于缓冲
SDWebImage提供了对图片缓存的支持,该功能由SDImageCache类负责。其中图片缓存分为(Memory)内存和(Disk)硬盘双重Cache机制。磁盘缓存的写操作是异步的,这样就不会对 UI 操作造成影响。
内存缓冲
内存缓冲主要是利用NSCache对象来实现的。NSCache是一个类似于集合的容器,提供了类似可变字典存储 key-value 对的实现方式,但它比可变字典更适用于实现缓存。
- 最重要的原因是NSCache是线程安全的,使用NSMutableDictionary自定义实现缓存的时候需要考虑加锁和释放锁,NSCache已经帮我们做好了这一步。
- 其次,内存不足时NSCache会自动释放存储的对象,不需要手动干预,如果是自定义实现需要监听内存状态然后做进一步删除对象的操作。
- 还有一点NSCache的键key不会被复制,所以key不需要实现NSCopying协议。
磁盘缓冲
使用NSFileManager对象来实现的。图片存储的位置是位于Cache文件夹。另外,SDImageCache 还定义了一个串行队列,来异步存储图片。
SDImageCache 提供了大量方法来缓存、获取、移除及清空图片。而对于每个图片,为了方便地在内存或磁盘中对它进行这些操作,我们需要一个 key 值来索引它。 在内存中,我们将其作为 NSCache 的 key 值,而在磁盘中,我们用这个 key 作为图片的文件名。 对于一个远程服务器下载的图片,其 url 是作为这个 key 的最佳选择了。文章来源:https://www.toymoban.com/news/detail-453925.html
清理缓冲的策略
Disk(硬盘)缓存清理策略:SDWebImage 会在每次 APP 结束的时候执行清理任务。 清理缓存的规则分两步进行。 第一步先清除掉过期的缓存文件。 如果清除掉过期的缓存之后,空间还不够。 那么就继续按文件时间从早到晚排序,先清除最早的缓存文件, 直到剩余空间达到要求。文章来源地址https://www.toymoban.com/news/detail-453925.html
到了这里,关于【iOS】SDWebImage源码学习--未完的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!