SDWebImage源码分析

@[TOC](SDWebImage源码分析

  • 基本框架
    • UIKit层
    • 工具层
      • SDWebImageManager
      • SDImageCache
      • SDWebImageDownloader
      • downloadImageWithURL

基本框架

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 *)urlplaceholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)context;- (void)sd_setImageWithURL:(nullable NSURL *)urlcompleted:(nullable SDExternalCompletionBlock)completedBlock;- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholdercompleted:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionscompleted:(nullable SDExternalCompletionBlock)completedBlock;- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(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 *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(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 *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextsetImageBlock:(nullable SDSetImageBlock)setImageBlockprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(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];#endifif (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 *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(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,确保后续操作不影响原始的contextcontext = [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 *)operationurl:(nonnull NSURL *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(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 *)operationurl:(nonnull NSURL *)urloptions:(SDWebImageOptions)optionscontext:(SDWebImageContext *)contextcachedImage:(nullable UIImage *)cachedImagecachedData:(nullable NSData *)cachedDatacacheType:(SDImageCacheType)cacheTypeprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(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 {// 参数有效性检查:如果键为空,则直接回调失败并返回nilif (!key) {if (doneBlock) {doneBlock(nil, nil, SDImageCacheTypeNone); // 回调无结果,缓存类型为None}return nil;}// 如果查询的缓存类型无效(即None),也直接回调失败并返回nilif (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 *)urloptions:(SDWebImageDownloaderOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(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;
}

这段方法主要实现了以下几个功能点:

  1. 参数校验:确保URL不为空,否则立即回调错误信息。
  2. 操作复用与创建:根据URL查找现有的下载操作,如果不存在或不可复用,则创建一个新的下载操作。
  3. 回调处理:为下载操作添加进度和完成的回调处理,确保新添加的回调能在操作执行过程中正确触发。
  4. 线程安全与锁机制:使用锁来保护操作字典和操作内部的状态更改,防止多线程下的数据竞争问题。
  5. 下载令牌:创建并返回一个SDWebImageDownloadToken对象,便于外部管理或取消下载操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/7009.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信答题链接怎么做_新手也能快速上手制作

在数字营销日新月异的今天&#xff0c;如何有效吸引用户参与、提升品牌曝光度&#xff0c;成为了每一个营销人都在思考的问题。而微信答题链接&#xff0c;作为一种新兴的互动营销方式&#xff0c;正以其独特的魅力&#xff0c;在营销界掀起一股新的热潮。今天&#xff0c;就让…

从C向C++16——常见容器2

一.stack容器 1.stack理解 概念&#xff1a; stack是一种先进后出的数据结构&#xff0c;它只有一个出口。 它在C中也叫栈&#xff0c;类似于我们在《数据结构和算法》里面的栈&#xff0c;只不过在C中把其封装成库&#xff0c;我们可以直接使用。 注意&#xff1a;栈中只有…

【精品毕设推荐】搜索引擎的设计与实现

点击免费下载原文及代码 摘要 我们处在一个大数据的时代&#xff0c;伴随着网络信息资源的庞大&#xff0c;人们越来越多地注重怎样才能快速有效地从海量的网络信息中&#xff0c;检索出自己需要的、潜在的、有价值的信息&#xff0c;从而可以有效地在日常工作和生活中发挥作…

typescript中的BigInt,展开运算符,解构和可选链运算

BigInt&#xff0c;展开运算符&#xff0c;解构和可选链运算 BigInt javascript中支持两种数据类型&#xff0c; Number类型和BigInt类型。 JavaScript的七种原始数据类型&#xff0c;Undefined,Null,Boolean,String,Symbol,Number,BigInt JavaScript使用双精度64位浮点数格式…

【UnityRPG游戏制作】Unity_RPG项目之场景环境搭建和解析

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

事件知识图谱 - EventKGE_Event knowledge graph embedding with event causal transfer

EventKGE: Event knowledge graph embedding with event causal transfer 作者&#xff1a;Daiyi Li&#xff08;南航&#xff09; 来源&#xff1a;2023 Knowledge-Based Systems&#xff08;中科院一区&#xff0c;影响因子8.8&#xff09; 论文&#xff1a;[ScienceDirec…

开源聊天软件和 php版本的开源聊天软件的介绍

开源聊天软件 php 开源的PHP聊天软件有很多&#xff0c;这里我会列举几个常见的&#xff1a; Pidgin: 这是一个跨平台的即时消息客户端&#xff0c;它支持各种协议&#xff0c;包括XMPP协议。虽然它本身不是一个聊天软件&#xff0c;但它可以用来编写XMPP协议的聊天软件。 …

Linux中gcc/g++的使用

文章目录 前言gcc/g 前言 gcc和g即为编译器。其中gcc为c语言的编译器&#xff0c;只能编译c语言&#xff1b;g为c的编译器&#xff0c;既能编译c语言&#xff0c;又能编译c。 在前面的文章中&#xff0c;我们提到代码转换成可执行程序需要经过 预处理&#xff08;进行宏替换)…

一、Vagrant搭建相关环境

目录 一、创建Vagrant相关环境1.下载安装VirtualBox2.在BlOS中设置CPU虚拟化3.使用Vagrant新建linux虚拟机3.1下载Vagrant3.2Vagrant官方镜像仓库3.3使用Vagrant初始化一个centos7的虚拟机 4.设置固定ip地址 二、安装docker1.按照docker 三、docker安装一些中间件1.mysql安装2.…

从零开始学AI绘画,万字Stable Diffusion终极教程(六)

【第6期】知识补充 欢迎来到SD的终极教程&#xff0c;这是我们的第六节课&#xff0c;也是最后一节课 这套课程分为六节课&#xff0c;会系统性的介绍sd的全部功能&#xff0c;让你打下坚实牢靠的基础 1.SD入门 2.关键词 3.Lora模型 4.图生图 5.controlnet 6.知识补充 …

Linux环境创建普通用户,授权root权限。报错:usermod: group ‘sudo‘ does not exist

在Linux环境下&#xff0c;创建普通用户并授权root权限需要以下步骤&#xff1a; 1. 以root用户登录终端。 2. 执行以下命令创建一个新的用户&#xff0c;其中username为你想要创建的用户名&#xff0c;可根据实际情况自行更改。 adduser username 3. 设置该用户的密码&…

Stylus的引入

Stylus是一个CSS预处理器&#xff0c;它允许开发者使用更高级的语法来编写CSS&#xff0c;并提供了一些额外的功能来简化和增强CSS的编写过程。以下是关于Stylus的详解和引入方法的详细介绍&#xff1a; 一、Stylus的详解 特点和功能&#xff1a; 变量&#xff1a;允许你定义…

【C++】vector类的增删改查模拟实现(图例超详细解析!!!)

目录 一、前言 二、源码引入 三、vector的模拟实现 ✨实现框架 ✨前情提要 ✨Member functions —— 成员函数 ⚡构造函数 ⭐无参构造 ⭐迭代器区间构造 ⭐n个值构造 ⚡拷贝构造 ⚡运算符赋值重载 ⚡析构函数 ✨Element access —— 元素访问 ⚡operator[ ] …

springcloud整合nacos实现相同版本实例相互调用

springcloud整合nacos实现相同版本实例相互调用 注: 本文为自己学习研究总结&#xff0c;仅供参考&#xff0c;若有侵权&#xff0c;请及时联系本人 业务场景 有时候一些新的业务或者修改后的功能只开放给部分人访问&#xff0c;那么可以新老版本都部署&#xff0c;对于大多…

[AHK V2]WinEvent - 简单的检测窗口打开关闭、移动、最大化、最小化等

WinEvent简介 WinEvent 可以监视所有窗口或特定窗口的窗口事件。目前支持以下事件&#xff1a;显示、创建、关闭、激活、非激活、移动、开始移动、结束移动、最小化、还原、最大化。有关详细信息&#xff0c;请参见库中函数的注释。 该库最新版可在Git Hub上获得。 WinEvent.a…

VsCode插件 -- Power Mode

一、安装插件 1. 首先在扩展市场里搜索 Power Mode 插件&#xff0c;如下图 二、配置插件 设置 点击小齿轮 打上勾 就可以了 第二种设置方法 1. 安装完成之后&#xff0c;使用快捷键 Ctrl Shift P 打开命令面板&#xff0c;在命令行中输入 settings.json &#xff0c; 选择首…

通过maven命令行mvn的方式,下载依赖jar包

目录 目标步骤执行mvn命令 目标 有时通过idea-maven-reload all maven projects更新项目依赖时&#xff0c;会报错Could not find artifact xxx.xx:xxx.x:xxx.jar (https://repo1.maven.org/maven2/org/)。 此时可尝试通过mvn命令行进行依赖下载&#xff08;需要配置maven本地…

头歌java面向对象基础

第一关类的定义 package step1;// ---------------------Begin------------------------ public class Student{String name"李四";int age18;public void speak(){System.out.println("我爱学习");} }// ---------------------End----------------------…

【Linux】安装Python3.11报错

文章目录 问题解决 问题 在centos系统使用make命令安装python 3.11.8时&#xff0c;报错了&#xff1a; Python runtime state: initialized Traceback (most recent call last):File "/usr/local/Python-3.11.8/Lib/site.py", line 73, in <module> Fatal P…

【Python深度学习(第二版)(2)】深度学习之前:机器学习简史

文章目录 一. 深度学习的起源1. 概率建模--机器学习分类器2. 早期神经网络--反向传播算法的转折3. 核方法 -- 忽略神经网络4. 决策树、随机森林和梯度提升机5. 神经网络替代svm与决策树 二. 深度学习与机器学习有何不同 可以这样说&#xff0c;当前工业界所使用的大部分机器学习…