1、整体框架 看下整体的框架图: 从图中可以更清晰的看出,SDWebImage
库是围绕SDWebImageManager
、SDWebImageCache
、SDWebImageDownloader
三个类展开的,而这三个类又是以SDWebImageManager
作为核心类。
下面就重点分析SDWebImageManager
:SDWebImageManager
是 SDWebImage
的管理以及操作类。也是SDWebImage
的核心类,拥有一个SDWebImageCache
和 SDWebImageDownloader
属性,分别用于图片缓存和下载处理。
2、SDWebImageManager
类分析 先看下.h文件 2.1、公共枚举 该枚举定义了图像加载的可选项:options 选项(枚举类型)
SDWebImageRetryFailed = 1 << 0,
默认情况下当通过URL下载图片失败后,该URL就被加入黑名单,之后SDWebImage不会再去尝试下载。此标志作用就是禁用该黑名单,也就是说使用SDWebImageRetryFailed后,图片下载失败仍会尝试下载
SDWebImageLowPriority = 1 << 1,
默认情况下图片在UI交互期间下载,此标志的作用就是禁用该功能。例如:在UIScrollView减速时导致延迟下载。
SDWebImageCacheMemoryOnly = 1 << 2,
此标志作用是:图片下载完成后仅缓存到内存,不缓存在磁盘上
SDWebImageCacheMemoryOnly = 1 << 2,
此标志作用是:图片下载完成后仅缓存到内存,不缓存在磁盘上
SDWebImageProgressiveDownload = 1 << 3,
此标志启用渐进式下载,图像在下载过程中逐步显示,就像浏览器一样。 默认情况下,图像仅在完全下载后显示。
SDWebImageRefreshCached = 1 << 4,
即使缓存中存在该图片,也尊重HTTP响应缓存控制,在需要时从远程刷新图片。 磁盘缓存将由 NSURLCache
替代 SDWebImage
去处理,这样也将导致性能略有下降。 该选项有助于处理同一个URL请求但更换图片的情况,例如Facebook图形api配置文件的图片。 如果刷新了缓存图片,则使用缓存图片调用一次完成block代码块,再使用最终的图片调用完成block代码块。 仅当你无法使用嵌入式缓存清除参数使URL保持静态时,才使用该标志。
SDWebImageContinueInBackground = 1 << 5,
在iOS 4+
中,如果app进入后台也继续下载图片,这是通过询问系统实现的。 在后台有额外的时间让请求完成,如果后台任务到期,则操作被取消。
SDWebImageHandleCookies = 1 << 6,
通过NSMutableURLRequest.HTTPShouldHandleCookies = YES
设置处理在 NSHTTPCookieStore
中的cookies。
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
启用允许不受信任的SSL证书(用于测试目的,在生产中谨慎使用)
SDWebImageHighPriority = 1 << 8,
默认情况下,图像按其排队顺序加载,此标志将它们移动到队列的前面
SDWebImageDelayPlaceholder = 1 << 9,
默认情况下,加载图像时会加载占位符。 此标志将延迟加载占位符图像,直到图像加载完毕。
SDWebImageTransformAnimatedImage = 1 << 10,
我们通常不会在动画图像上调用transformDownloadedImage委托方法,因为大多数转换代码会破坏它。如果使用必须使用此标志来转换它们。
SDWebImageAvoidAutoSetImage = 1 << 11,
默认情况下,下载后会将图像添加到imageView。 但在某些情况下,我们想要 在设置图像之前手动处理一些东西(例如应用滤镜或添加交叉渐变动画)请使用此标志
SDWebImageScaleDownLargeImages = 1 << 12,
默认情况下,图像会根据其原始大小进行解码。 在iOS上,此标志将缩小图像尺寸与设备的受限内存兼容。(如果设置了“SDWebImageProgressiveDownload”标志,则停用缩小比例。)
SDWebImageQueryDataWhenInMemory = 1 << 13,
默认情况下,当图像缓存在内存中时,我们不查询磁盘数据。 此掩码可以强制同时查询磁盘数据。 建议将此标志与SDWebImageQueryDiskSync
一起使用,以确保图像在同一个runloop中加载。
SDWebImageQueryDiskSync = 1 << 14,
默认情况下,我们同步查询内存缓存,异步查询磁盘缓存。 此掩码可以强制同步查询磁盘缓存,以确保在同一个runloop中加载映像。 如果禁用内存缓存或在某些其他情况下,此标志可以避免在单元重用期间闪烁。
SDWebImageFromCacheOnly = 1 << 15,
默认情况下,当缓存丢失时,将从网络下载映像。此标志可以阻止网络仅从缓存加载。
SDWebImageForceTransition = 1 << 16
默认情况下,当您使用 SDWebImageTransition
在图像加载完成后进行某些视图转换时,此转换仅适用于从网络下载图像。 此掩码也可以强制为内存和磁盘缓存应用视图转换。
2.2、公共类型定义 四个回调代码块:
1 2 3 4 5 6 7 8 9 10 11 12 //用于外部分类中完成回调的block typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL); //用于该类内部完成回调的block typedef void(^SDInternalCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL); //将url处理成缓存图像用的key的方法,返回字符串作为存储时的key,可以删除url中的产讯字段 //用于在使用某url生成key之前,先把url的某些动态信息除掉,以便可以用简洁一点的url来生成key。因此这个block的内容就是对url的操作,返回值是一个简洁版的url。 typedef NSString * _Nullable(^SDWebImageCacheKeyFilterBlock)(NSURL * _Nullable url); //将图像缓存到磁盘的解码算法,返回的是data。 typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL);
2.3、协议:SDWebImageManagerDelegate
使用协议的优点在于:
不需要担心循环引用问题
有利于程序的结构化与层次化
有利于代码的封装
SDWebImageManager
提供的三个都是可选协议,这也是我们平时常用的方式,遵守协议的类根据需要实现协议方法,不需要编译器警告提示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 /** * 当缓存没有发现当前图片,会查看调用者是否实现该方法,如果返回NO,则不会继续下载该图片 * * @param imageManager 当前的 `SDWebImageManager` * @param imageURL 应该下载的图像的URL * * @return 返回结果,返回NO时阻止去下载,如果不执行,默认的是YES Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied. */ - (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL; /** * 当图片下载失败时,会查看调用者是否将该图片URL标记为失败,如果返回YES,则将此URL标记为失败。 注意:如果委托实现此方法,将不会使用内置方式根据错误代码将URL标记为失败; @param imageManager 当前的`SDWebImageManager` @param imageURL 图像的网址 @param error URL下载的错误 @return 是否阻止失败的URL再去下载,返回YES,将URL标记为失败,不再下载 */ - (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldBlockFailedURL:(nonnull NSURL *)imageURL withError:(nonnull NSError *)error; /** * 允许在下载后立即转换图像,然后将图像缓存到磁盘和内存中。 * 注意:从全局队列调用此方法,以便不阻止主线程。 * * @param imageManager 当前的`SDWebImageManager` * @param image 要转换的图像 * @param imageURL 要转换的图像的网址 * * @return 转换后的图像对象。 */ - (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;
2.4、协议:属性和方法 声明属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 //代理 @property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate; //图片缓存 @property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache; //图片下载 @property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader; //缓存过滤器代码块(每次SDWebImageManager将URL转换为缓存所需要的key时使用的代码块,这个可以用于删除图片URL的动态部分) @property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter; //高速缓存序列化器,用于将解码图像(愿下载数据)转换为用于存储到磁盘高速缓存的世纪数据的代码块,如果返回nil,则表示从图像实例生成数据。 @property (nonatomic, copy, nullable) SDWebImageCacheSerializerBlock cacheSerializer; //全局SDWebImageManager实例。 + (nonnull instancetype)sharedManager; //允许指定与图像管理器一起使用的缓存和图像下载器的实例。 //返回带有指定缓存和下载器的`SDWebImageManager`的新实例。 - (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader NS_DESIGNATED_INITIALIZER; /** * 如果缓存中不存在,就下载给定URL的图像,存在就返回缓存的版本 Downloads the image at the given URL if not present in cache or return the cached version otherwise. * * @param url 图像的URL * @param options 用于指定此请求的选项掩码 * @param progressBlock 下载图像时调用的代码块(在后台队列上执行的进度代码块) * * @param completedBlock 操作完成时调用的代码块 * @return 返回 SDWebImageDownloaderOperation 的一个实例 completedBlock 即:typedef void(^SDInternalCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL); 该代码块没有返回值,将请求到的UIImage作为第一个参数;NSData作为第二个参数;如果出错,则image参数为nil,第三个参数可能包含NSError;第四个参数是一个`SDImageCacheType`枚举,指明图像的来源(本地缓存中检索、内存缓存、网络下载。)第五个参数用来判断下载是否完成;最后一个参数为原始图像的URL */ - (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock; /** * 将图像保存到给定的URL的缓存中 * * @param image要缓存的图像 * @param url 图像的URL * */ // - (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url; //取消当前所有的操作 - (void)cancelAll; //检查一个或多个正在运行的操作 - (BOOL)isRunning; /** * 异步检查图像是否已被缓存 * * @param url 图像 url * @param completionBlock 检查完成时要执行的代码块 * * @note 完成代码块总是在主队列上执行 */ - (void)cachedImageExistsForURL:(nullable NSURL *)url completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock; /** * 异步检查图像是否已仅缓存在磁盘上 * * @param url 图像 url * @param completionBlock 检查完成时要执行的代码块 * * @note 完成代码块总是在主队列上执行 */ - (void)diskImageExistsForURL:(nullable NSURL *)url completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock; //返回给定URL的缓存key - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;
再看.m文件 的声明 2.5 SDWebImageCombinedOperation
的属性声明和协议方法实现 定义一个SDWebImageCombinedOperation
类,该类遵循 SDWebImageOperation
协议 ,该协议仅定义了一个cancel的方法。 之所以在这里定义这个类,而不是直接使用SDWebImageDownloaderOperation
类来表示下载任务,原因有两方面:
为了可以在下面使用中修改cancelled
属性的值,在SDWebImageDownloaderOperation
中 cancelled
属于对外是只读的
一个操作表示一个获取图像的动作,通常优先从缓存中取出图像,缓存中没鱼哦才需要下载,而SDWebImageDownloderOperation
是专门用来下载的,没有包含查找缓存的功能。
1 2 3 4 5 6 7 8 9 10 @interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation> //是否已取消 @property (assign, nonatomic, getter = isCancelled) BOOL cancelled; //下载任务 @property (strong, nonatomic, nullable) SDWebImageDownloadToken *downloadToken; //取消操作 @property (strong, nonatomic, nullable) NSOperation *cacheOperation; //管理类 @property (weak, nonatomic, nullable) SDWebImageManager *manager; @end
实现SDWebImageOperation
协议的cancel的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @implementation SDWebImageCombinedOperation - (void)cancel { @synchronized(self) { //设置为取消状态 self.cancelled = YES; //取消的操作存在,就执行该取消操作,并置为nil if (self.cacheOperation) { [self.cacheOperation cancel]; self.cacheOperation = nil; } //在管理类的下载任务中取消该下载任务 if (self.downloadToken) { //在下载SDWebImageDownloader类中执行取消任务操作 [self.manager.imageDownloader cancel:self.downloadToken]; } //安全移除该操作 [self.manager safelyRemoveOperationFromRunning:self]; } } - (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation { if (!operation) { return; } //加锁是为了安全,防止正在移除该操作时有变化 LOCK(self.runningOperationsLock); [self.runningOperations removeObject:operation]; UNLOCK(self.runningOperationsLock); }
2.6 SDWebImageManager
的属性声明 1 2 3 4 5 6 7 8 @interface SDWebImageManager () @property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache; @property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader; @property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs; @property (strong, nonatomic, nonnull) dispatch_semaphore_t failedURLsLock; // a lock to keep the access to `failedURLs` thread-safe @property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations; @property (strong, nonatomic, nonnull) dispatch_semaphore_t runningOperationsLock; // a lock to keep the access to `runningOperations` thread-safe @end
imageCache
和 imageDownloader
在.h文件中已经声明,但是只读类型。这里重复声明成读写类型,实现外部为只读,内部为读写类型。
两个可变集合体failedURLs
和 runningOperations
前者存储NSURL
类型,记录失败的urls,后者存储SDWebImageCombinedOperation
类型,记录正在运行的操作。
两个信号量failedURLsLock
和 runningOperationsLock
锁,为了保持访问failedURLs
和 runningOperations
线程安全的。
2.7、SDWebImageManager
的方法实现 2.7.1、初始化 三个初始化方法,单例模式确保全局只有一个实例,避免每次调用时创建新的实例。
使用GCD中的dispatch_once
创建的实例对象必须确保只有一个,所以使用static修饰 static dispatch_once_t once; static id instance;
dispatch_once
可以简化代码且保证线程安全,开发者无需担心加锁或同步。所有问题都在GCD底层处理。此外,dispatch_once
更高效。它没有使用重量级的同步机制。使用同步机制,每次运行代码都需要获取锁。dispatch_once
采用“原子访问”来查询标记,判断代码是否执行过。
[self new]
是一种比较老式的写法,而alloc/init
的引入则是因为new
不够灵活,因为使用new
的话,会使得初始化方法被固定死只能调用init
。而这里就是使用的new
方法,所以调用的顺序只能是sharedManager ---> init ---> initWithCache: downloader:
。
在init
初始化中,可以看出cache
和 downloader
也是单例模式,其实也是使用的new
方法,原理和这里的相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 //单例模式 + (nonnull instancetype)sharedManager { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } //初始化 cache 和 downloader - (nonnull instancetype)init { SDImageCache *cache = [SDImageCache sharedImageCache]; SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader]; return [self initWithCache:cache downloader:downloader]; } //这里实现一些属性的初始化。 - (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader { if ((self = [super init])) { _imageCache = cache; _imageDownloader = downloader; _failedURLs = [NSMutableSet new]; _failedURLsLock = dispatch_semaphore_create(1); _runningOperations = [NSMutableSet new]; _runningOperationsLock = dispatch_semaphore_create(1); } return self; }
2.7.2、功能实现方法
通过URL获取缓存时使用的key 利用Image的URL生成一个缓存时需要的key. 如果检测到cacheKeyFilter不为空时,利用cacheKeyFilter来处理URL生成一个key;否则直接返回URL的string内容,当做key.
1 2 3 4 5 6 7 8 9 10 11 - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url { if (!url) { return @""; } if (self.cacheKeyFilter) { return self.cacheKeyFilter(url); } else { return url.absoluteString; } }
图像放缩操作:根据图片中的图片组 或 scale 重新计算返回图片。该方法专门使用了一个类(SDWebImageCompat
)来实现。
1 2 3 - (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image { return SDScaledImageForKey(key, image); }
缓存图像
通过URL缓存图像到内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 - (void)cachedImageExistsForURL:(nullable NSURL *)url completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock { //获取缓存所需的key NSString *key = [self cacheKeyForURL:url]; //判断内存中是否已缓存该图像 BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil); //如果已经缓存,在主线程中执行完成回调代码块 if (isInMemoryCache) { // making sure we call the completion block on the main queue dispatch_async(dispatch_get_main_queue(), ^{ if (completionBlock) { completionBlock(YES); } }); return; } //如果还未缓存,则调用 diskImageExistsWithKey,该方法会将图像缓存到磁盘中(在存储之前也会判断磁盘中是否已缓存该图像),该方法在异步线程执行。 [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch if (completionBlock) { completionBlock(isInDiskCache); } }]; }
将图像缓存到磁盘中
1 2 3 4 5 6 7 8 9 10 11 - (void)diskImageExistsForURL:(nullable NSURL *)url completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock { NSString *key = [self cacheKeyForURL:url]; [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch if (completionBlock) { completionBlock(isInDiskCache); } }]; }
将图像缓存到内存中
1 2 3 4 5 6 - (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url { if (image && url) { NSString *key = [self cacheKeyForURL:url]; [self.imageCache storeImage:image forKey:key toDisk:YES completion:nil]; } }
取消掉所有正在执行的操作Operation
1 2 3 4 5 6 - (void)cancelAll { LOCK(self.runningOperationsLock); NSSet<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy]; UNLOCK(self.runningOperationsLock); [copiedOperations makeObjectsPerformSelector:@selector(cancel)]; // This will call `safelyRemoveOperationFromRunning:` and remove from the array }
判断是否有正在运行的操作Operation
1 2 3 4 5 6 7 - (BOOL)isRunning { BOOL isRunning = NO; LOCK(self.runningOperationsLock); isRunning = (self.runningOperations.count > 0); UNLOCK(self.runningOperationsLock); return isRunning; }
安全移除正在运行的操作Operation
1 2 3 4 5 6 7 8 - (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation { if (!operation) { return; } LOCK(self.runningOperationsLock); [self.runningOperations removeObject:operation]; UNLOCK(self.runningOperationsLock); }
回调方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation completion:(nullable SDInternalCompletionBlock)completionBlock error:(nullable NSError *)error url:(nullable NSURL *)url { [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url]; } - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation completion:(nullable SDInternalCompletionBlock)completionBlock image:(nullable UIImage *)image data:(nullable NSData *)data error:(nullable NSError *)error cacheType:(SDImageCacheType)cacheType finished:(BOOL)finished url:(nullable NSURL *)url { dispatch_main_async_safe(^{ if (operation && !operation.isCancelled && completionBlock) { completionBlock(image, data, error, cacheType, finished, url); } }); }
通过url建立一个operation用来下载图片. 返回operation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 - (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { // Invoking this method without a completedBlock is pointless NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead"); //在没有completedBlock的情况下调用此方法毫无意义 , NSAssert(completedBlock!= nil,@“如果你的意思是预取图像,请使用 - [SDWebImagePrefetcher prefetchURLs]代替”); // 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. (非常常见的错误是使用NSString对象而不是NSURL发送URL。 出于某些奇怪的原因,Xcode不会抛出此类型不匹配的任何警告。在这里,我们通过允许URL作为NSString传递来确保此错误。) if ([url isKindOfClass:NSString.class]) { //如果传入的url是字符串类型,则转换成NSURL类型 url = [NSURL URLWithString:(NSString *)url]; } // Prevents app crashing on argument type error like sending NSNull instead of NSURL //防止应用程序崩溃类型错误,如发送NSNull而不是NSURL,则url置为nil if (![url isKindOfClass:NSURL.class]) { url = nil; } 初始化一个SDWebImageCombinedOperation对象 SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; //设置它的SDWebImageManager 等于self operation.manager = self; BOOL isFailedUrl = NO; //如果url存在,判断该url是否失败过,并赋值给isFailedUrl;加锁是为了安全,防止在判断的过程中被其他线程修改 if (url) { LOCK(self.failedURLsLock); isFailedUrl = [self.failedURLs containsObject:url]; UNLOCK(self.failedURLsLock); } //判断条件:如果url不存在,或者options不是SDWebImageRetryFailed(失败重试)并且isFailedUrl 为 YES。就回调一个error的block (异常处理),并返回operation //error为:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) { [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url]; return operation; } //创建互斥锁,执行runningOperations添加operation操作 LOCK(self.runningOperationsLock); [self.runningOperations addObject:operation]; UNLOCK(self.runningOperationsLock); //获取缓存所需要的key NSString *key = [self cacheKeyForURL:url]; // 下面都是判断我们的cacheOptions里包含哪些SDWebImageOptions,然后给我们的cacheOptions相应的添加对应的SDImageCacheOptions. cacheOptions |= SDImageCacheQueryDataWhenInMemory这种表达式的意思等同于cacheOptions = cacheOptions | SDImageCacheQueryDataWhenInMemory SDImageCacheOptions cacheOptions = 0; if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory; if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync; if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages; //弱引用 __weak SDWebImageCombinedOperation *weakOperation = operation; /* *如果图片是从内存加载,则返回的cacheOperation是nil, *如果是从磁盘加载,则返回的cacheOperation是`NSOperation`对象。 *如果是从网络加载,则返回的cacheOperation对象是`SDWebImageDownloaderOperation`对象。 */ operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) { //强引用,防止被提前释放 __strong __typeof(weakOperation) strongOperation = weakOperation; //operation不存在或者已经被取消,返回并移除该operation对象 if (!strongOperation || strongOperation.isCancelled) { [self safelyRemoveOperationFromRunning:strongOperation]; return; } // Check whether we should download image from network //检查是否应该从网络下载图像 BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly)) && (!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]); //应该从网络下载图像 if (shouldDownload) { //如果缓存图像存在,但options设置为SDWebImageRetryFailed。则先返回缓存图像 if (cachedImage && options & SDWebImageRefreshCached) { // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. //构建回调block [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; } // download if no image or requested to refresh anyway, and download allowed by delegate /* 把图片加载的`SDWebImageOptions`类型枚举转换为图片下载的`SDWebImageDownloaderOptions`类型的枚举 */ SDWebImageDownloaderOptions downloaderOptions = 0; if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload; 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; /* 如果设置了强制刷新缓存的选项。则`SDWebImageDownloaderProgressiveDownload`选项失效并且添加`SDWebImageDownloaderIgnoreCachedResponse`选项。 */ if (cachedImage && options & SDWebImageRefreshCached) { // force progressive off if image already cached but forced refreshing //如果图像已缓存但强制刷新,则强制渐进关闭 downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // ignore image read from NSURLCache if image if cached but force refreshing //忽略从NSURLCache读取的图像,如果图像缓存但强制刷新 downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } // `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle __weak typeof(strongOperation) weakSubOperation = strongOperation; /* 新建一个网络下载的操作。 */ strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { __strong typeof(weakSubOperation) strongSubOperation = weakSubOperation; //如果操作被取消或者不存在,则不执行任何操作 if (!strongSubOperation || strongSubOperation.isCancelled) { // Do nothing if the operation was cancelled // See #699 for more details // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data } else if (error) { //如果加载出错。则直接返回回调。并且添加到failedURLs中 [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock error:error url:url]; BOOL shouldBlockFailedURL; // Check whether we should block failed url //检查我们是否应该阻止失败的网址 if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) { shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error]; } else { shouldBlockFailedURL = ( error.code != NSURLErrorNotConnectedToInternet && error.code != NSURLErrorCancelled && error.code != NSURLErrorTimedOut && error.code != NSURLErrorInternationalRoamingOff && error.code != NSURLErrorDataNotAllowed && error.code != NSURLErrorCannotFindHost && error.code != NSURLErrorCannotConnectToHost && error.code != NSURLErrorNetworkConnectionLost); } //如果shouldBlockFailedURL为YES,即阻止失败的网址,添加到failedURLs中 if (shouldBlockFailedURL) { LOCK(self.failedURLsLock); [self.failedURLs addObject:url]; UNLOCK(self.failedURLsLock); } } else { //加载图片成功, //如果options选项设置为SDWebImageRetryFailed,则把url从failedURLS中移除 if ((options & SDWebImageRetryFailed)) { LOCK(self.failedURLsLock); [self.failedURLs removeObject:url]; UNLOCK(self.failedURLsLock); } //是否缓存在磁盘上 BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); // We've done the scale process in SDWebImageDownloader with the shared manager, this is used for custom manager and avoid extra scale.(我们已经使用共享管理器在SDWebImageDownloader中完成了缩放过程,这用于自定义管理器并避免额外的扩展。) //如果self不为SDWebImageManager单例,并且cacheKeyFilter(缓存可以代码块存在,下载的图像存在),则缩放下载的图像 if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) { downloadedImage = [self scaledImageForKey:key image:downloadedImage]; } //如果options选项为SDWebImageRefreshCached,并且缓存图像存在,下载图像不存在。图像刷新命中NSURLCache缓存,不调用完成块 if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block //图像刷新命中NSURLCache缓存,不调用完成块 } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) { //如果成功下载图片。并且图片是动态图片。并且设置了SDWebImageTransformAnimatedImage属性。则处理图片 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ //获取transform以后的图片 UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; //存储transform以后的的图片 if (transformedImage && finished) { BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; NSData *cacheData; // pass nil if the image was transformed, so we can recalculate the data from the image(如果图像被转换,则传递nil,因此我们可以重新计算图像中的数据) //cacheSerializer:将图像缓存到磁盘的解码算法,返回的是data。如果self.cacheSerializer存在,则重新计算图像中的数据。 if (self.cacheSerializer) { cacheData = self.cacheSerializer(transformedImage, (imageWasTransformed ? nil : downloadedData), url); } else { cacheData = (imageWasTransformed ? nil : downloadedData); } //存储transform以后的图片 [self.imageCache storeImage:transformedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil]; } //回调拼接 [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; }); } else {//如果成功下载图片。并且图片不是图片。则直接缓存和回调 if (downloadedImage && finished) { //cacheSerializer:将图像缓存到磁盘的解码算法,返回的是data。如果self.cacheSerializer存在,则重新计算图像中的数据。 if (self.cacheSerializer) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NSData *cacheData = self.cacheSerializer(downloadedImage, downloadedData, url); //存储transform以后的图片 [self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil]; }); } else { //存储transform以后的图片 [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil]; } } //回调拼接 [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; } } //从正在加载的图片操作集合中移除当前操作 if (finished) { [self safelyRemoveOperationFromRunning:strongSubOperation]; } }]; } else if (cachedImage) { //如果缓存的图像存在,回调拼接,安全移除Operation对象 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; [self safelyRemoveOperationFromRunning:strongOperation]; } else { // Image not in cache and download disallowed by delegate(委托不允许图像不在缓存和下载中) //如果缓存的图像不存在,回调拼接,安全移除Operation对象 [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url]; [self safelyRemoveOperationFromRunning:strongOperation]; } }]; return operation; }
2.8、SDWebImageCompat
的图像缩放操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 /** * 放缩操作:根据图片中的图片组 或 scale 重新计算返回图片 * * @param key 键:就是图片的地址 * @param image UIImage * * @return UIImage */ inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) { //图片为空,返回nil if (!image) { return nil; } #if SD_MAC //如果是MAC,直接返回图片 return image; #elif SD_UIKIT || SD_WATCH //iOS或者watch if ((image.images).count > 0) {//动态图 NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array]; for (UIImage *tempImage in image.images) { //动态图还是执行该方法,处理单个图片 [scaledImages addObject:SDScaledImageForKey(key, tempImage)]; } //创建一个动态图片,动态图片持续的时间为duration UIImage *animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration]; if (animatedImage) { animatedImage.sd_imageLoopCount = image.sd_imageLoopCount; animatedImage.sd_imageFormat = image.sd_imageFormat; } return animatedImage; } else { #if SD_WATCH if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)]) { #elif SD_UIKIT if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { #endif // 比如屏幕为320x480时,scale为1,屏幕为640x960时,scale为2 CGFloat scale = 1; // “@2x.png”的长度为7,所以此处添加了这个判断,很巧妙 if (key.length >= 8) { //根据后缀给scale赋值 NSRange range = [key rangeOfString:@"@2x."]; if (range.location != NSNotFound) { scale = 2.0; } range = [key rangeOfString:@"@3x."]; if (range.location != NSNotFound) { scale = 3.0; } } // 使用initWithCGImage来根据Core Graphics的图片构建UIImage。 // 这个函数可以使用scale和orientation UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation]; scaledImage.sd_imageFormat = image.sd_imageFormat; image = scaledImage; } return image; } #endif } NSString *const SDWebImageErrorDomain = @"SDWebImageErrorDomain";