@[TOC](SDWebImage源码分析
基本框架
SDWebImage作为一个著名的iOS图像加载库,其源码主要包括以下几个核心部分:
- 图片下载管理:SDWebImageManager 是整个库的核心管理类,负责协调图片下载、缓存和处理。
- 缓存模块:SDImageCache 提供了内存缓存和磁盘缓存功能。
- 图片解码与处理:图片下载后,SDWebImage会对图片采取渐进式解码的方式加载图片。
- UIImageView分类:UIImageView (WebCache) 提供了一系列便捷的接口,如sd_setImageWithURL
- 多线程与异步处理:SDWebImage内部大量运用了GCD(Grand Central Dispatch)来处理并发任务,确保图片下载、缓存读写以及图像处理都是在后台线程执行,避免阻塞主线程影响UI流畅度。
通过网上的资料我简单的了解到该框架主要源码可以分为两部分:UIKit层和工具层
UIKit层的** UIImageView+WebCache和UIView+WebCache**为外部提供了下载图片的接口 ;
工具层的SDWImageManager是该库的核心类;它通过协调SDWebImageCache(负责缓存方面的工作),SDWebImageDownLoader(负责下载方面的工作) 实现任务的管理 ;
UIKit层(负责接收下载参数)和工具层(负责下载操作和缓存)
UIKit层
这部分源码主要包括UIImageView+WebCache和UIView+WebCache,由于UIImageView+WebCache中的接口方法的实现依赖于UIView+WebCache提供的接口 ,我们先从UIimageView+WebCahce来了解一下接口的功能和实现 ;
UIImageView+WebCache:
@interface UIImageView (WebCache)
- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
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 NS_REFINED_FOR_SWIFT;
- (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;
/**
* 通过指定的URL设置imageView的`image`属性,同时支持占位图、自定义下载选项以及上下文参数。
*
* 图片下载过程为异步,并且会进行缓存。
*
* @param url 图片的URL地址。
* @param placeholder 图片请求完成前要设置的初始占位图片。
* @param options 下载图片时使用的选项。有关所有可能的选项值,请参见`SDWebImageOptions`。
* @param context 一个包含不同选项以执行特定改变或过程的上下文对象,参考`SDWebImageContextOption`。此上下文用于持有`options`枚举无法容纳的额外对象。
* @param progressBlock 图片正在下载时调用的进度回调块。
* @note 进度块在后台队列中执行。
* @param completedBlock 操作完成时调用的回调块。此块没有返回值,其第一个参数为请求的UIImage对象;若发生错误,该参数为nil,第二个参数可能包含NSError对象;第三个参数是一个布尔值,表示图片是从本地缓存还是网络获取的;第四个参数是原始的图片URL。
*/
- (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;
通过观察ImageView+Cache.m中各个方法的实现,发现前面所有的接口都调用了
- (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+Cache中的
- (nullable id)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
该方法的实现如下:
- (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 {
// 确保context为不可变对象,并为缺失的context提供默认值
context = context ? [context copy] : [NSDictionary dictionary];
// 设置唯一操作键用于追踪操作
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey] ?: NSStringFromClass([self class]);
self.sd_latestOperationKey = validOperationKey;
// 取消当前视图上与该键关联的任何现有加载操作
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
self.sd_imageURL = url;
// 确定SDWebImageManager实例,优先使用自定义管理器或共享管理器
SDWebImageManager *manager = context[SDWebImageContextCustomManager] ?: [SDWebImageManager sharedManager];
// 避免循环引用
if (context[SDWebImageContextCustomManager]) {
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextCustomManager] = nil;
context = [mutableContext copy];
}
// 弱引用缓存逻辑,根据配置决定是否触发
BOOL shouldUseWeakCache = manager.imageCache isKindOfClass:[SDImageCache class]] ? ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache : NO;
// 如果不需要延迟占位图,且使用弱引用缓存,则预先触发一次内存缓存查询以确保同步逻辑
if (!(options & SDWebImageDelayPlaceholder) && shouldUseWeakCache) {
NSString *key = [manager cacheKeyForURL:url context:context];
[((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) {
// 重置进度条
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (imageProgress) {
imageProgress.totalUnitCount = 0;
imageProgress.completedUnitCount = 0;
}
// UI相关的指示器逻辑
#if SD_UIKIT || SD_MAC
[self sd_startImageIndicator];
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
// 合并进度回调,包含UI更新和用户自定义的进度回调
SDImageLoaderProgressBlock combinedProgressBlock = ^(...){ ... };
// 使用SDWebImageManager加载图片
operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
// 一系列逻辑处理,包括进度更新、指示器停止、图片设置、完成回调
// ...
// 根据条件设置图片,调用自定义设置图片block,处理过渡效果等
// ...
// 调用完成回调
callCompletedBlockClosure();
}];
// 记录当前操作
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
// 处理URL为空的情况,停止指示器,调用完成回调
#if SD_UIKIT || SD_MAC
[self sd_stopImageIndicator];
#endif
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
dispatch_main_async_safe(^{
completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
});
}
}
return operation;
}
在这个方法中总体做了下面这几件事:
1.根据key取消当前的操作
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
从SDOperationsDictionary中根据key获取到operation执行cancle,并从SDOperationsDictionary中remove这个key对应的operation。
2.将url作为属性绑定到UIView上
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
3.根据URL,通过SDWebImageManager的loadImageWithURL方法加载图片
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
...
}
4.将上一步得到的operation,存入SDOperationsDictionary中
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
下面看几个我比较在意的点:
// 弱引用缓存逻辑,根据配置决定是否触发
BOOL shouldUseWeakCache = manager.imageCache isKindOfClass:[SDImageCache class]] ? ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache : NO;
这里的弱引用缓存逻辑通过网络查询大概知道什么意思,但还是直接用别人的话讲了:
弱引用缓存逻辑是指在SDWebImage库中处理图片缓存的一种策略,特别涉及到内存缓存部分。通常,内存缓存在iOS开发中用于临时存储最近或频繁访问的数据,以便快速访问,提高应用性能。内存缓存中的对象会被强引用,这意味着只要缓存中的对象被引用,它们就不会被自动释放,即使在低内存情况下也可能占用大量内存资源。
而弱引用缓存逻辑是一种优化措施,它允许缓存中的图片对象被弱引用(weak reference)而不是强引用。这意味着一旦图片不在其他地方被强引用(例如,不再显示在UI上),即使它们还在缓存中,也可以被系统自动回收以释放内存。这在内存管理上更加灵活和高效,尤其适用于内存紧张的场景,能减少内存泄漏的风险,并帮助应用避免因内存压力过大而被系统终止。
在SDWebImage中,通过判断SDImageCache实例的配置属性shouldUseWeakMemoryCache来决定是否启用弱引用缓存模式。如果启用,即使图片被缓存,当系统需要回收内存时,这些图片对象可以被适时释放,从而保证应用的内存使用更加合理和高效。不过,这也意味着被弱引用的图片在系统进行内存回收时可能更早消失,下次访问时可能需要重新从磁盘或网络加载。
// 如果不需要延迟占位图,且使用弱引用缓存,则预先触发一次内存缓存查询以确保同步逻辑
if (!(options & SDWebImageDelayPlaceholder) && shouldUseWeakCache) {
NSString *key = [manager cacheKeyForURL:url context:context];
[((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
// 立即显示占位图
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
预先触发一次内存缓存查询以确保同步逻辑,主要是为了优化用户体验并提高效率。但这里的作用更重要的是确认同步逻辑,更详细点就是
在某些情况下,特别是当使用弱引用缓存策略时,直接查询内存缓存可以帮助确保缓存状态与当前加载策略同步。弱引用缓存意味着对缓存对象的持有不保证其生命周期,因此即时查询可以确认预期的图片是否已经存在于缓存中,这对于后续的加载决策(比如是否需要从磁盘或网络加载)至关重要。
// 使用SDWebImageManager加载图片
operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
// 一系列逻辑处理,包括进度更新、指示器停止、图片设置、完成回调
// ...
// 根据条件设置图片,调用自定义设置图片block,处理过渡效果等
// ...
// 调用完成回调
callCompletedBlockClosure();
}];
这段代码内部细节没有详细描述,知道功能就行 ;
工具层
SDWebImageManager同时管理着SDIMageCache和SDWebImageDownloader两个类,SDWebImageManager首先会通过SDImagerCache类来查询缓存,如果缓存中没有找到对应的图片就会通过SDWebImageloader来下载图片,下载下来的图片会存入缓存,然后显示。在分析这三个类的前提下,我们先了解一下这个管理类 ;
SDWebImageManager
首先我们要先知道该类的属性:
@interface SDWebImageManager ()
// 使用SD_LOCK_DECLARE宏定义了一个锁,用于保护_failedURLs集合的并发访问,确保多线程环境下的数据一致性。
// 当需要修改或访问_failedURLs时,会先获取这个锁,操作完成后释放锁,以此来防止数据竞争问题。
SD_LOCK_DECLARE(_failedURLsLock);
// 同样,这是另一个锁,用于保护_runningOperations集合的并发访问。
// _runningOperations集合用于跟踪当前所有正在执行的下载或缓存操作,确保对它的操作也是线程安全的。
SD_LOCK_DECLARE(_runningOperationsLock);
// 属性声明:
// _imageCache:这是一个强引用的SDImageCache对象,负责图片的磁盘缓存和内存缓存。
// SDImageCache是SDWebImage中用于存储和检索图片的核心组件,支持内存和磁盘两级缓存策略。
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
// _imageLoader:一个遵循SDImageLoader协议的对象,负责实际的图片加载任务。
// 这个协议定义了如何从网络或其他源加载图片的方法,SDWebImage内部提供了几个默认实现,如SDWebImageDownloader。
@property (strong, nonatomic, readwrite, nonnull) id<SDImageLoader> imageLoader;
// _failedURLs:这是一个可变的NSMutableSet,用于记录所有下载失败的图片URL。
// 当下载操作因错误而失败时,对应的URL会被加入此集合,这有助于实现重试逻辑或错误处理。
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
// _runningOperations:包含所有正在进行的SDWebImageCombinedOperation对象的集合。
// SDWebImageCombinedOperation是一个复合操作,结合了从缓存读取和网络下载两个子操作,
// 用于协调图片加载的整体流程。维护这个集合可以用来取消所有操作(如视图即将被销毁时)或监控当前的加载状态。
@property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations;
@end
简单的了解一下上面各个属性所负责的功能 ,之后的两个类都会用到 ;
这个类的方法很多,这里具体讲一个和功能实现相关的方法,也是从url网络下载图片的方法,同上面的- (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;
其他的许多方法都依赖于这个方法 ;
url网络下载图片的方法:
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
// 确保completedBlock不为空,因为没有完成回调意味着无法处理加载结果
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// 将可能传入的NSString类型的URL转换为NSURL,以防类型错误
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// 如果URL不是NSURL类型,则设为nil以避免后续错误
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
// 创建一个新的SDWebImageCombinedOperation实例,该操作组合了缓存查询和网络下载
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self; // 关联操作与当前manager
// 检查URL是否在失败列表中,使用锁保证线程安全
BOOL isFailedUrl = NO;
if (url) {
SD_LOCK(_failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
SD_UNLOCK(_failedURLsLock);
}
// 处理options和context,生成最终的处理结果
SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
// 如果URL为空或属于黑名单且不重试失败链接,则直接调用完成回调并返回操作
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}] queue:result.context[SDWebImageContextCallbackQueue] url:url];
return operation;
}
// 将新创建的操作添加到正在运行的操作集中,同样需要加锁以确保线程安全
SD_LOCK(_runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(_runningOperationsLock);
// 开始加载图片的流程,首先尝试从缓存中获取
// 此步骤会根据是否有transformer(图片处理器)选择不同的加载路径
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
// 返回操作实例,外部可以通过此操作取消加载等
return operation;
}
这段方法首先进行了一系列的预检查和初始化,然后根据图片URL是否有效、是否需要重试失败的URL等条件,决定是否直接调用完成回调还是继续执行图片加载流程。它通过SDWebImageCombinedOperation来封装了缓存查询和网络下载的复合操作,并利用锁机制维护了failedURLs和runningOperations集合的线程安全性,确保了整个加载过程的高效和稳定。
这里用到了两个新方法:
// 处理options和context,生成最终的处理结果
SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
// 此步骤会根据是否有transformer(图片处理器)选择不同的加载路径
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
第一个方法负责返回url处理后的结果,第二个方法就真正进入了图片的加载流程了 ;
首先看第一个方法(返回已处理的操作结果,包括指定图像URL的操作和content)的实现部分:
- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
SDWebImageOptionsResult *result; // 初始化结果对象,用于存放处理后的选项和上下文
// 创建一个可变的上下文字典,用于临时存储可能添加的默认处理器信息
SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];
// 检查并设置默认的图片转换器(Transformer),如果用户没有自定义的话
if (!context[SDWebImageContextImageTransformer]) {
id<SDImageTransformer> transformer = self.transformer; // 从manager中获取默认的图片转换器
[mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer]; // 添加到mutableContext中
}
// 类似地,检查并设置默认的缓存键过滤器(Cache Key Filter)
if (!context[SDWebImageContextCacheKeyFilter]) {
id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter; // 获取默认的缓存键过滤器
[mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter]; // 添加到mutableContext中
}
// 检查并设置默认的缓存序列化器(Cache Serializer)
if (!context[SDWebImageContextCacheSerializer]) {
id<SDWebImageCacheSerializer> cacheSerializer = self.cacheSerializer; // 获取默认的缓存序列化器
[mutableContext setValue:cacheSerializer forKey:SDWebImageContextCacheSerializer]; // 添加到mutableContext中
}
// 如果mutableContext中有新增的内容(即有默认处理器被添加)
if (mutableContext.count > 0) {
// 把用户传入的context也合并到mutableContext中,这样就包含了用户自定义和默认的所有处理器
if (context) {
[mutableContext addEntriesFromDictionary:context];
}
// 然后把mutableContext拷贝给context,确保后续操作不影响原始的context
context = [mutableContext copy];
}
// 应用自定义的选项处理器(如果有的话),这个处理器可以进一步修改加载图片的选项和上下文
if (self.optionsProcessor) {
result = [self.optionsProcessor processedResultForURL:url options:options context:context];
}
// 如果没有自定义处理器,或者处理器没有返回结果,使用默认的选项和上下文创建结果对象
if (!result) {
result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
}
// 最终返回处理后的选项和上下文结果
return result;
}
这个方法的主要目的是确保图片加载请求具有所有必需的处理逻辑(如图片转换、缓存键过滤、缓存序列化等),同时允许用户自定义覆盖这些默认行为。通过这种方式,SDWebImage提供了高度灵活和可定制的图片加载机制。
第二个方法(查询普通缓存进程)的实现:
// 查询正常缓存流程的方法
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// 获取用于查询的图片缓存,默认使用manager的imageCache,但如果context中指定了则优先使用那个
id<SDImageCache> imageCache = context[SDWebImageContextImageCache] ?: self.imageCache;
// 获取查询缓存的类型,默认为全部缓存(内存+磁盘),但如果context中指定了则使用指定类型
SDImageCacheType queryCacheType = context[SDWebImageContextQueryCacheType] ? [context[SDWebImageContextQueryCacheType] integerValue] : SDImageCacheTypeAll;
// 判断是否需要查询缓存,如果options中没有指定仅从loader加载,则需要查询缓存
BOOL shouldQueryCache = !(options & SDWebImageFromLoaderOnly);
if (shouldQueryCache) {
// 计算转换后的缓存键,用于查询缓存
NSString *key = [self cacheKeyForURL:url context:context];
// 使用弱引用避免循环引用,确保operation在block执行完后可以被正确释放
@weakify(operation);
// 查询缓存
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"}] queue:context[SDWebImageContextCallbackQueue] url:url];
[self safelyRemoveOperationFromRunning:operation];
return;
}
// 缓存中无图片,且转换后的键与原始键不同,说明有可能原图在缓存中,尝试查询原图缓存
else if (!cachedImage && ![key isEqualToString:[self originalCacheKeyForURL:url context:context]]) {
[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];
}
}
// 使用弱引用避免循环引用,确保operation在block执行完后可以被正确释放
@weakify(operation);
这个地方的循环引用,从operation.manager = self; // 关联操作与当前manager可以明白 ;
// 继续下载流程,无论图片是否从缓存中获取到
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
这个地方的逻辑是先从缓存中寻找,但即使寻找到,也要通过网络下载更新图片或者后面再进行判断 ;
从第二个方法再深入一下,我们可以看到上面只是对缓存进行了查找,直到最后调用的函数才开始了url下载 :- (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
方法实现:
- (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;
}
// 获取图片加载器,优先使用context中的,否则使用manager的默认加载器
id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader] ?: self.imageLoader;
// 判断是否应该从网络下载图片
BOOL shouldDownload = !(options & SDWebImageFromCacheOnly);
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
shouldDownload &= ([self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] ? [self.delegate imageManager:self shouldDownloadImageForURL:url] : YES);
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) {
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];
// 将缓存图片传给加载器,以便比较远程图片是否相同
SDWebImageMutableContext *mutableContext = context ? [context mutableCopy] : [NSMutableDictionary dictionary];
mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
context = [mutableContext copy];
}
// 使用弱引用避免block内的循环引用
@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 (finished) {
[self safelyRemoveOperationFromRunning:operation];
}
}];
} else {
// 如果不应该下载(比如只从缓存读取或代理禁止下载),根据是否有缓存图片调用完成回调
if (cachedImage) {
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];
} else {
[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];
}
[self safelyRemoveOperationFromRunning:operation];
}
}
此方法首先确保了缓存操作的引用被正确清理,然后基于传入的选项和上下文判断是否应当从网络下载图片。如果决定下载,它会调用图片加载器发起下载请求,并通过一系列逻辑处理下载过程中的各种情况,包括缓存刷新、错误处理、完成后的图片转换流程等。如果决定不下载(例如因为设置了仅从缓存加载或者代理方法不允许下载),则直接根据是否有缓存图片调用完成回调。整个方法通过弱引用和强引用的转换有效避免了潜在的循环引用问题。
SDImageCache
同理,先了解该类的属性:
这段代码是SDImageCache类的一个类别(Category)定义,用于声明一些私有的属性。下面是这段代码的详细注释:
```objective-c
// 静态字符串,用于存储默认的磁盘缓存目录路径。这是一个类级别的变量,所有SDImageCache实例共享。
static NSString * _defaultDiskCacheDirectory;
// 开始定义SDImageCache类的一个匿名类别,主要用于声明私有属性
@interface SDImageCache ()
#pragma mark - Properties
// 强引用类型,遵循SDMemoryCache协议的对象,用于存储内存中的图片缓存。
@property (nonatomic, strong, readwrite, nonnull) id<SDMemoryCache> memoryCache;
// 强引用类型,遵循SDDiskCache协议的对象,用于存储磁盘上的图片缓存。
@property (nonatomic, strong, readwrite, nonnull) id<SDDiskCache> diskCache;
// 拷贝属性,存储SDImageCache的配置对象,包含了诸如缓存大小限制、缓存路径等配置信息。
@property (nonatomic, copy, readwrite, nonnull) SDImageCacheConfig *config;
// 拷贝属性,存储磁盘缓存的具体路径。
@property (nonatomic, copy, readwrite, nonnull) NSString *diskCachePath;
// 强引用类型,一个GCD的队列,用于执行I/O相关的操作,如磁盘读写,确保这些操作在后台线程执行,不阻塞主线程。
@property (nonatomic, strong, nonnull) dispatch_queue_t ioQueue;
@end
其实实现部分最重要的一个函数- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock
用与查询缓存:
```objective-c
// 定义查询缓存操作的方法,接受键、查询选项、上下文和完成回调作为参数,可能返回一个缓存查询令牌
- (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key
options:(SDImageCacheOptions)options
context:(nullable SDWebImageContext *)context
cacheType:(SDImageCacheType)queryCacheType
done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
// 参数有效性检查:如果键为空,则直接回调失败并返回nil
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone); // 回调无结果,缓存类型为None
}
return nil;
}
// 如果查询的缓存类型无效(即None),也直接回调失败并返回nil
if (queryCacheType == SDImageCacheTypeNone) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 内存缓存查询
UIImage *image;
if (queryCacheType != SDImageCacheTypeDisk) { // 允许从内存查询时尝试获取图片
image = [self imageFromMemoryCacheForKey:key];
}
// 图片处理逻辑:根据查询选项调整图片(如仅解码第一帧、匹配动画图类)
// ...
// 确定是否仅查询内存
BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory); // 回调内存缓存的结果
}
return nil;
}
// 磁盘缓存查询准备
SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue];
SDImageCacheToken *operation = [[SDImageCacheToken alloc] initWithDoneBlock:doneBlock]; // 创建操作令牌
operation.key = key;
operation.callbackQueue = queue;
// 定义查询磁盘数据和图片的块
NSData* (^queryDiskDataBlock)(void) = ^NSData* { ... }; // 查询磁盘数据
UIImage* (^queryDiskImageBlock)(NSData*) = ^UIImage*(NSData* diskData) { ... }; // 根据数据生成图片
// 异步或同步执行磁盘查询逻辑
if (shouldQueryDiskSync) { // 同步查询
// 执行同步查询逻辑
...
} else { // 异步查询
// 使用I/O队列异步执行查询,确保磁盘操作不阻塞主线程
dispatch_async(self.ioQueue, ^{
// 查询磁盘数据和生成图片的逻辑
...
// 安全回调,确保操作未被取消
if (doneBlock) {
[(queue ?: SDCallbackQueue.mainQueue) async:^{
if (!operation.isCancelled) {
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
}
}];
}
});
}
// 返回操作令牌,允许外部控制查询操作
return operation;
}
SDWebImageDownloader
这个类负责图片的下载 ;
先了解属性:
```objc
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
// 创建一个操作队列,用于管理下载任务的并发执行
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
// 用于存储下载操作的字典,键为图片的URL,值为对应的下载操作对象。
// 这样做可以方便地追踪和管理每个URL的下载过程。
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, NSOperation<SDWebImageDownloaderOperation> *> *URLOperations;
// 存储HTTP头信息的字典,键是头部字段名,值是对应的字段值。
// 这些头部信息可能会在发起网络请求时用到,以便定制化请求或处理服务器认证等。
@property (strong, nonatomic, nullable) NSMutableDictionary<NSString *, NSString *> *HTTPHeaders;
// 定义一个NSURLSession实例,所有的数据下载任务都在这个会话中执行。
// NSURLSession提供了高级的网络通信功能,如后台传输服务、上传下载进度跟踪等。
@property (strong, nonatomic) NSURLSession *session;
@end
这段代码是关于SDWebImageDownloader
类的一个匿名分类,用于声明一些私有属性和遵循的协议。它主要涉及到网络图片下载的相关配置和管理,包括任务队列、操作追踪、HTTP头信息及网络会话的管理。
首先我们先来看到下面这个方法
downloadImageWithURL
downloadImageWithURL方法返回的是一个SDWebImageDownloadToken类型的token,这么做的目的是,可以在取消的回调中及时取消下载操作 ;
这段代码是SDWebImage库中`SDWebImageDownloader`类的一个方法实现,用于根据指定的URL异步下载图片,并支持进度监控和完成回调。以下是详细的注释:
```objc
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// 参数验证:URL不能为空,否则直接调用完成回调并传入错误信息
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;
}
// 准备工作:根据上下文获取缓存键过滤器和解码选项
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
NSString *cacheKey = cacheKeyFilter ? [cacheKeyFilter cacheKeyForURL:url] : url.absoluteString;
SDImageCoderOptions *decodeOptions = SDGetDecodeOptionsFromContext(context, [self.class imageOptionsFromDownloaderOptions:options], cacheKey);
// 线程安全:加锁处理当前下载操作字典
SD_LOCK(_operationsLock);
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
// 判断是否应该复用或创建新的下载操作
BOOL shouldNotReuseOperation = operation ? (@synchronized (operation) { operation.isFinished || operation.isCancelled || SDWebImageDownloaderOperationGetCompleted(operation) }) : YES;
if (shouldNotReuseOperation) {
// 创建新的下载操作
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) {
SD_LOCK(self->_operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self->_operationsLock);
}
};
// 添加到操作队列,并关联进度与完成回调
[self.URLOperations setObject:operation forKey:url];
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock decodeOptions:decodeOptions];
[self.downloadQueue addOperation:operation];
} else {
// 复用现有下载操作,安全地添加新的进度和完成回调
@synchronized (operation) {
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock decodeOptions:decodeOptions];
}
}
SD_UNLOCK(_operationsLock);
// 创建并返回下载令牌,包含操作详情供外部控制或追踪
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
这段方法主要实现了以下几个功能点:
- 参数校验:确保URL不为空,否则立即回调错误信息。
- 操作复用与创建:根据URL查找现有的下载操作,如果不存在或不可复用,则创建一个新的下载操作。
- 回调处理:为下载操作添加进度和完成的回调处理,确保新添加的回调能在操作执行过程中正确触发。
- 线程安全与锁机制:使用锁来保护操作字典和操作内部的状态更改,防止多线程下的数据竞争问题。
- 下载令牌:创建并返回一个
SDWebImageDownloadToken
对象,便于外部管理或取消下载操作。