官网 https://github.com/rs/SDWebImage 版本 4.1.2
简介
SDWebImage是iOS开发者经常使用的一个开源框架,这个框架的主要作用是:
一个异步下载图片并且支持缓存(也可自定义路径缓存).
与版本 3.8比较
SDWebImage 4.1.2 比3.8版本多了这几个文件。
- NSImage+WebCache.h : 关于Image 的 gif 的判断
- SDImageCacheConfig.h:关于缓存的配置(缓存到memory的时间, 是否缓存到 Memery)
- UIView+WebCache.h:把之前 UIImageView+WebCache ,UIButton 通用的部分提取出来。把 UIImageView中的关于Activity indicator方法整理 到 UIView 里面,这样就拓展了 UIButton
- UIView+WebCache.h中的下面这个方法,UIImageView &UIButton 都有用到。
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
之前 UIButton 没有关于activity UIActivityIndicatorView 的方法. 并且sd_cancelCurrentImageLoad 在 UIImageView+WebCache 和 UIButton+WebCache中都有声明及实现。
- (void)sd_cancelCurrentImageLoad;
#if SD_UIKIT
#pragma mark - Activity indicator
/**
* Show activity UIActivityIndicatorView显示ActivityIndicatorView
*/
- (void)sd_setShowActivityIndicatorView:(BOOL)show;
/**
* set desired UIActivityIndicatorViewStyle
*
* @param style The style of the UIActivityIndicatorView
*/
- (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style;
- (BOOL)sd_showActivityIndicatorView;
- (void)sd_addActivityIndicator;
- (void)sd_removeActivityIndicator;
常用方法:以[imageView sd_setImageWithURL:picURL placeholderImage:nil];
为例
由于- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder
的实现直接调用的一个方法
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
及
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:nil
progress:progressBlock
completed:completedBlock];
}
那么,现在以下面 UIView 中的方法为例:
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
现在开始一步一步看该方法的内部实现:
- 首先先执行
sd_cancelImageLoadOperationWithKey:
//如果没有给定operationKey,那么获取类的名字,以它为 key
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
//移除UIView等拓展类(这里主要是UIImageView 和 UIButton)当前绑定的操作.当TableView的cell包含的UIImageView被重用的时候首先执行这一行代码,保证这个ImageView或者 button的下载和缓存组合操作都被取消
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
接下来 看看sd_cancelImageLoadOperationWithKey:
方法的实现
下面我们先来看看UIView (WebCacheOperation)这个类别提供的方法:用于操作绑定关系
/**
* Set the image load operation (storage in a UIView based dictionary)
* 设置图像加载操作(存储在和UIView做绑定的字典里面)
* @param operation the operation
* @param key key for storing the operation
*/
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key;
/**
* Cancel all operations for the current UIView and key
* 用这个key找到当前UIView上面的所有操作移除并取消
* @param key key for identifying the operations
*/
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key;
/**
* Just remove the operations corresponding to the current UIView and key without cancelling them
* 用这个key找到当前UIView上面的所有操作仅仅移除,不取消。
* @param key key for identifying the operations
*/
- (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key;
objc_setAssociatedObject 作用是对已存在的类在扩展中添加自定义的属性,通常推荐的做法是添加属性的key最好是static char类型的,通常来说该属性的key应该是常量唯一的。
objc_getAssociatedObject 作用根据key获得与对象绑定的属性。
- (SDOperationsDictionary *)operationDictionary {
//objc_getAssociatedObject根据key获得与对象绑定的属性。
/*
这个loadOperationKey 的定义是:static char loadOperationKey;
它对应的绑定在UIView的属性是operationDictionary(NSMutableDictionary类型)
operationDictionary的value是操作,key是针对不同类型视图和不同类型的操作设定的字符串
注意:&是一元运算符结果是右操作对象的地址(&loadOperationKey返回static char loadOperationKey的地址)
*/
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
//如果可以查到operations,就返回,反之给视图绑定一个新的,空的operations字典
if (operations) {
return operations;
}
operations = [NSMutableDictionary dictionary];
//objc_setAssociatedObject作用是对已存在的类在扩展中添加自定义的属性,通常推荐的做法是添加属性的key最好是static char类型的,通常来说该属性的key应该是常量唯一的。
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
// 根据 key 值 取消正在下载的队列
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self operationDictionary];
/**
如果 operationDictionary可以取到,根据key可以得到与视图相关的操作,取消他们
并根据key值,从operationDictionary里面删除这些操作
*/
id operations = operationDictionary[key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}
接下来我们继续探索- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
直接简写成sd_internalSetImageWithURL
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
//如果没有给定operationKey,那么获取类的名字,以它为 key
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
//移除UIView等拓展类(这里主要是UIImageView 和 UIButton)当前绑定的操作.当TableView的cell包含的UIImageView被重用的时候首先执行这一行代码,保证这个ImageView的下载和缓存组合操作都被取消
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//若options参数不是SDWebImageDelayPlaceholder,即options不等于SDWebImageDelayPlaceholder,延迟加载占位图一直到图像完成加载意思也就是说图像显示占位图,就执行以下操作
if (!(options & SDWebImageDelayPlaceholder)) {
//dispatch_main_async_safe是一个宏定义,因为图像的绘制只能在主线程完成,所以dispatch_main_sync_safe就是为了保证block在主线程中执行
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
注意: dispatch_main_async_safe 这是一个宏定义。之所以提示一下是因为版本3.8和4.1.2版本代码不一样,作用一样
V4.1.2
获取当前进程的 name 和主线程的相比较,如果相等,则strcmp == 0;即在主线程进行回调 block
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif
V3.8
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
V4.1.2 采用的 GCD 的方式,效率稍高。
- 接下来 ,继续
sd_internalSetImageWithURL
if (url) {
// check if activityView is enabled or not
// 检查是否通过`setShowActivityIndicatorView:`方法设置了显示正在加载指示器。如果设置了,使用`addActivityIndicator`方法向self添加指示器
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
#pragma mark - 下载的核心方法 -
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
//完成下载,移除加载指示器
[sself sd_removeActivityIndicator];
//如果这个控件不存在了,就 返回,即暂停
if (!sself) {
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
/**
SDWebImageAvoidAutoSetImage,默认情况下图片会在下载完毕后自动添加给该控件self,
但是有些时候我们想在设置图片之前加一些图片的处理,就要下载成功后去手动设置图片了,不会执行`wself.image = image;`,而是直接执行完成回调,由用户自己决定如何处理。
*/
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
//如果后两个条件中至少有一个不满足,那么就直接将image赋给当前的控件,并调用setNeedsLayout
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
//image为空,并且设置了延迟设置占位图,会将占位图设置为最终的image,并将其标记为需要重新布局。
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
//为self 控件绑定新的操作,把之前该控件的操作cancel了
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
// 判断url不存在,移除加载指示器,执行完成回调,传递错误信息。
dispatch_main_async_safe(^{
[self sd_removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
其中下载方法- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
位于SDWebImageManager 类中,V3.8 中方法名为
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;downloadImageWithURL:.....
SDWebImageManager:这个 SDWebImage 进行操作的核心类。对图片的下载和缓存管理
- (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");
// 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类型的url,如果url的类型是NSString就给转换成NSURL类型
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// 如果转换NSURL失败,就把传入的url置为nil下载停止
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
__block用于指明当前声明的变量在被block捕获之后,可以在block中改变变量的值。因为在block声明的同时会截获该block所使用的全部自动变量的值,这些值只在block中只有”使用权”而不具有”修改权”。而block说明符就为block提供了变量的修改权,block不能避免循环引,这就需要我们在 block 内部将要退出的时候手动释放掉 blockObj,blockObj = nil
__weak是所有权修饰符, __weak本身是可以避免循环引用的问题的,但是其会导致外部对象释放之后,block内部也访问不到对象的问题,我们可以通过在block内部声明一个__strong的变量来指向weakObj,使外部既能在block内部保持住又能避免循环引用
SDWebImageCombineOperation
- SDWebImageCombineOperation : 什么也不做,保存了、(一个block,可以取消下载operation;一个operation,cacheOperation用来下载图片并且缓存的operation;保存了一个状态isCancelled)
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;
@end
SDWebImageCombineOperation 的实现
@implementation SDWebImageCombinedOperation
- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {
//检查cancelBlock 的 operation 是否已经取消,如果没有取消,则调用cancleblock
// check if the operation is already cancelled, then we just call the cancelBlock
if (self.isCancelled) {
if (cancelBlock) {
cancelBlock();
}
//记得 cancelblock 为 nil 否则 crash
_cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
} else {
_cancelBlock = [cancelBlock copy];
}
}
- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock();
// TODO: this is a temporary fix to #809.
// Until we can figure the exact cause of the crash, going with the ivar instead of the setter
// self.cancelBlock = nil;
_cancelBlock = nil;
}
}
回到==SDWebImageManager==
/**
self.failedURLs是一个NSSet类型的集合,里面存放的都是下载失败的图片的url,failedURLs不是NSArray类型的原因是:在搜索一个个元素的时候NSSet比NSArray效率高,主要是它用到了一个算法hash(散列,哈希) ,比如你要存储A,一个hash算法直接就能找到A应该存储的位置;同样当你要访问A的时候,一个hash过程就能找到A存储的位置,对于NSArray,若想知道A到底在不在数组中,则需要遍历整个数据,显然效率较低了
*/
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
if (url) {
// @synchronized是OC中一种方便地创建互斥锁的方式--它可以防止不同线程在同一时间执行区块的代码--创建一个互斥锁防止现有的别的线程修改failedURLs
// 判断这个url是否是fail过的,如果url failed过的,containsObject : 返回 true,即isFailedUrl就是true.
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
}
/**如果url不存在那么直接返回一个block;
如果url存在那么继续 判断 !(options & SDWebImageRetryFailed)
它的意思看这个options是不是和SDWebImageRetryFailed不相同,如果不相同并且isFailedUrl是true(这个 url 是 存在请求失败的 URL 集合里面).那么就回调一个error的block,直接返回 operation
*/
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;
}
```
如果上面的条件不满足,即url的字符串长度>0
```
//把operation加入到self.runningOperations的数组里面,并创建一个互斥线程锁来保护这个操作
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
接下来进行一系列的操作。
// 获取image的url对应的key
NSString *key = [self cacheKeyForURL:url];
//是SDImageCache的一个方法,根据图片的key,异步查询磁盘缓存的方法
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
if (operation.isCancelled) {
// 安全移除 operation 这比 v3版本 要严谨。
[self safelyRemoveOperationFromRunning:operation];
return;
}
//条件1(!cachedImage || options & SDWebImageRefreshCached):在缓存中没有找到图片或者options == SDWebImageRefreshCached(这两项都需要进行请求网络图片的)
//条件2:代理允许下载执行imageManager:shouldDownloadImageForURL:方法,SDWebImageManagerDelegate的delegate不能响应imageManager:shouldDownloadImageForURL:方法;;或者能响应方法且方法返回值为YES.也就是没有实现这个方法就是允许的,如果实现了的话,返回YES才是允许
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
/**
如果在缓存中找到了image且options选项包含SDWebImageRefreshCached,
先在主线程完成一次回调,使用的是缓存中找的图片
*/
if (cachedImage && options & SDWebImageRefreshCached) {
// 如果在缓存中找到了image但是设置了SDWebImageRefreshCached选项,传递缓存的image,同时尝试重新下载它来让NSURLCache有机会接收服务器端的更新
// 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.
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
// 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载
// download if no image or requested to refresh anyway, and download allowed by delegate
SDWebImageDownloaderOptions downloaderOptions = 0;
// 开始各种 options 的判断。
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;
// 如果 image 已经被缓存但是需要设置options = SDWebImageRefreshCached(请求服务器刷新),强制关闭渐进式选项
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
//如果image已经被缓存但是设置了需要请求服务器刷新的选项,忽略从NSURLCache读取的image
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
//创建下载操作,先使用self.imageDownloader下载
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
//如果strongOperation == nil 或者strongOperation操作取消了,不做任何事情
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.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) {
//进行完成回调
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
//将url添加到失败列表里面
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
//如果设置了下载失败重试,将url从失败列表中去掉,if 条件options == SDWebImageRetryFailed
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//options包含了SDWebImageRefreshCached选项,且缓存中找到了image且没有下载成功
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// 图片刷新遇到了NSSURLCache中有缓存的状况,不调用 回调
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
///图片下载成功 并且 设置了需要变形Image(gif)的选项且变形的代理方法已经实现
全局队列异步执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
调用代理方法完成图片transform
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
// pass nil if the image was transformed, so we can recalculate the data from the image
// 对已经transform的图片进行缓存
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
//主线程执行完成回调
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
// 如果没有图片transform的需求,且图片下载完成且图片存在就直接缓存
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
//主线程完成回调
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
// finished 完成,从正在进行的操作列表中移除这组合操作
if (finished) {
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
//设置组合操作取消得得回调
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
} else if (cachedImage) {
//在缓存中找到图片(代理不允许下载 或者没有设置SDWebImageRefreshCached选项 满足至少一项)
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
// 从正在进行的操作列表中移除组合操作
[self safelyRemoveOperationFromRunning:operation];
} else {
// 缓存中没有找到图片且代理不允许下载
// Image not in cache and download disallowed by delegate
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
这里用到的方法callCompletionBlockForOperation:...
只不过是对下面的封装。实现:
dispatch_main_async_safe(^{
if (operation && !operation.isCancelled && completionBlock) {
completionBlock(image, data, error, cacheType, finished, url);
}
});
但是版本 V3.8.x里面是这样实现的都用的同步的方法,没有对其封装 ,如下
dispatch_main_sync_safe(^{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
两者都确保更新 ui 在主线程更新, V3.8.x 是同步,V4.1.x是异步
这里有用到SDImageCache、SDWebImageDownloader
SDImageCache
在项目中使用的单例,其真正的初始化方法:
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory {
if ((self = [super init])) {
// 这是 cache 缓存的 地方,一个文件夹。
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
//创建一个自定义的串行队列。DISPATCH_QUEUE_SERIAL代表的是创建一个串行的队列,所以_ioQueue是一个串行(DISPATCH_QUEUE_SERIAL)队列(任务一个执行完毕才执行下一个)
// Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
_config = [[SDImageCacheConfig alloc] init];
// Init the memory cache
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace;
// Init the disk cache
这里directory :沙盒 /Library/Caches/default
if (directory != nil) {
// ../Library/Caches/default/com.hackemist.SDWebImageCache.default
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
}
dispatch_sync(_ioQueue, ^{
_fileManager = [NSFileManager new];
});
#if SD_UIKIT
// Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
}
return self;
}
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
// 路径是沙盒下,/Library/Caches 的地址 ,在 default
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths[0] stringByAppendingPathComponent:fullNamespace];
}
存储的方法
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}
// 当前队列是不是初始化的 IO队列
[self checkIfQueueIsIOQueue];
// 如果缓存的文件夹不存在,这创建一个文件夹。
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// get cache Path for image key
// 根据当前图片的 key 值 根据某种算法得出对应的缓存图片 的路径名字
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
// 对 image进行保存
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// disable iCloud backup
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
defaultCachePathForKey:
里面调用:
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
// 以16进制格式命名
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];
return filename;
}
SDWebImageManager里面用到的 SDImageCache中 store 的方法;
key值 是在SDWebImageManager中得到的,如下:(一般是 url.absoluteString)
// 如果检测到cacheKeyFilter不为空的时候,利用cacheKeyFilter来生成一个key
//如果为空,那么直接返回URL的string内容,当做key.
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
if (!url) {
return @"";
}
if (self.cacheKeyFilter) {
return self.cacheKeyFilter(url);
} else {
return url.absoluteString;
}
}