SDWebImage 4.0
流程(以UIImageView+WebCache为例)
UIImage+WebCache
这个分类只是提供了设置图片的一些方法,真正的实现是在UIView+WebCache
.
由于SDWebImage
也提供UIButton+WebCache
,所以将所有的实现都放在了UIView
这个父类中来实现.
UIImage+WebCache
中提供了:
- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options;
- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullabel SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderimage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock;
url
: 图片url
placeholder
: 占位图片
options
: 策略, 枚举值:
//默认情况下,如果一个url在下载的时候失败了,那么这个url会被加入黑名单并且library不会尝试再次下载,这个flag会阻止library把失败的url加入黑名单(简单来说如果选择了这个flag,那么即使某个url下载失败了,sdwebimage还是会尝试再次下载他
SDWebImageRetryFailed = 1 << 0,
//默认情况下,图片会在交互发生的时候下载(例如你滑动tableview的时候),这个flag会禁止这个特性,导致的结果就是在scrollview减速的时候,才会开始下载(也就是你滑动的时候scrollview不下载,你手从屏幕上移走,scrollview开始减速的时候才会开始下载图片
SDWebImageLowPriority = 1 << 1,
//这个flag禁止磁盘缓存,只有内存缓存
SDWebImageCacheMemoryOnly = 1 << 2,
//这个flag会在图片下载的时候就显示(就像你用浏览器浏览网页的时候那种图片下载,一截一截的显示(待确认))
SDWebImageProgressiveDownload = 1 << 3,
//一个图片即使已经缓存了,还是会根据url进行重新请求.并且缓存策略依据NSURLCache而不是SDWebImage,
SDWebImageRefreshCached = 1 << 4,
//启动后台下载,加入你进入一个页面,有一张图片正在下载这时候你让app进入后台,图片还是会继续下载(这个估计要开backgroundfetch才有用)
SDWebImageContinueInBackground = 1 << 5,
//可以控制存在NSHTTPCookieStore的cookies.
SDWebImageHandleCookies = 1 << 6,
//允许不安全的SSL证书,在正式环境中慎用
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
//默认情况下,image在装载的时候是按照他们在队列中的顺序装载的(就是先进先出).这个flag会把他们移动到队列的前端,并且立刻装载,而不是等到当前队列装载的时候再装载.
SDWebImageHighPriority = 1 << 8,
//默认情况下,占位图会在图片下载的时候显示.这个flag开启会延迟占位图显示的时间,等到图片下载完成之后才会显示占位图.
SDWebImageDelayPlaceholder = 1 << 9,
//是否transform图片
SDWebImageTransformAnimatedImage = 1 << 10,
//默认情况下,图片是在下载完成后加载到图片视图。但是在一些情况下,我们想要在设置图片之前进行图片处理(比如,提供一个过滤或添加一个折叠动画)。使用这个标志,如果你想在下载成功后在完成块中手动设置图片。
SDWebImageAvoidAutoSetImage = 1 << 11,
//默认情况下,图片解码为原始的大小。在iOS,这个标志会把图片缩小到与设备的受限内容相兼容的大小。如果设置了SDWebImageProgressDownload标志,那么缩小被设置为无效。
SDWebImageScaleDownLargeImages = 1 << 12,
//默认情况下,当图像缓存在内存中时,我们不会查询磁盘数据。这个flag可以强制同时查询磁盘数据。此标志建议与“SDWebImageQueryDiskSync”一起使用,以确保图像在相同的runloop中加载
SDWebImageQueryDataWhenInMemory = 1 << 13,
//默认情况下,我们同步查询内存缓存,异步查询磁盘缓存。这个flag可以强制同步查询磁盘缓存,以确保在同一个runloop中加载图片。如果禁用内存缓存或在其他情况下禁用内存缓存,此flag可以避免在cell重用期间。
SDWebImageQueryDiskSync = 1 << 14,
//默认情况下,当缓存丢失时,图像将从网络下载。此flag仅可防止网络从缓存加载。
SDWebImageFromCacheOnly = 1 << 15,
//默认情况下,当您在图像加载完成后使用' SDWebImageTransition '来做一些视图转换时,这个转换只适用于从网络下载图像。这个flag还可以强制为内存和磁盘缓存应用视图转换。
SDWebImageForceTransition = 1 << 16
progressBlock
: 下载进度回调
completedBlock
: 完成回调
UIView+WebCache
这些方法最终都会走到UIView+WebCache
中的方法:
- (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 context:(nullable NSDictionary<NSString *, id> *)context;
operationKey
: 要用作operation
的key
的字符串。如果为nil
,将使用类名
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
setImageBlock
: block
用于自定义集图像代码
context
: 带有额外信息以执行指定更改或流程的内容.
这个方法才是真正视图层执行的方法.
首先判断拿到operationKey
,取消这个对应的operation
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
UIView+WebCacheOperation
这个方法存在于UIView+WebCacheOperation
中,从存储中拿到对应的operation
,如果遵循SDWebImageOperation
协议,就先cancel
,然后再从存储中移除.
这个分类中用到了NSMapTable
(NSMapTable(后续添加)).
同时还用到了@synchronized
(锁(后续添加))和关联对象(关联对象(后续添加)).
通过关联对象创建NSMapTable
类型的关联对象loadOperationKey
(加锁操作),用于存储operation
.
在这分类中,存取移除都进行了加锁操作.
存储的operation
都是遵循SDWebImageOperation
协议的,这个协议中只有取消操作.
然后创建imageURLKey
关联对象.
下一步如果设置了占位图并且策略也设置了占位图,进行设置占位图(主线程更新UI
).
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
再下一步判断url
,如果为nil
,直接返回错误信息,不为nil
则继续进行下一步.
重置下载进度.
// reset the progress
self.sd_imageProgress.totalUnitCount = 0;
self.sd_imageProgress.completedUnitCount = 0;
获取SDWebImageManager
单例对象, 这就进入了管理类
SDWebImageManager
首先设置进度管理block
.
__weak __typeof(self)wself = self;
SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
wself.sd_imageProgress.totalUnitCount = expectedSize;
wself.sd_imageProgress.completedUnitCount = receivedSize;
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
manager
进行下载操作.
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock;
这个方法中,首先进行SDWebImageCombinedOperation
这个operation
的创建,设置manager
.创建SDWebImageManager
与SDWebImageCombinedOperation
的依赖,其中SDWebImageCombinedOperation
设置了SDWebImageOperation
的代理,实现cancel
方法.
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self;
判断是否是错误url
. SDWebImageManager
持有一个NSMutableSet
类型的错误对象,用于保存错误url
.
BOOL isFailedUrl = NO;
if (url) {
LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
UNLOCK(self.failedURLsLock);
}
将这个SDWebImageCombinedOperation
这个对象加入到manager
持有的runningOperations
中.
LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
UNLOCK(self.runningOperationsLock);
获取缓存策略SDImageCacheOptions
, 这是一个枚举值:
//默认情况下,当图片缓存在内存中时,我们不会查询磁盘数据。这个mask可以强制同时查询磁盘数据
SDImageCacheQueryDataWhenInMemory = 1 << 0,
//默认情况下,我们同步查询内存缓存,异步查询磁盘缓存。这个mask可以强制同步查询磁盘缓存
SDImageCacheQueryDiskSync = 1 << 1,
//默认情况下,图像会根据原始大小进行解码。在iOS系统中,这个flag会将图像缩小到与设备有限的内存兼容的大小
SDImageCacheScaleDownLargeImages = 1 << 2
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory;
if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
SDImageCache
再接下来就进入到SDImageCache
这个类中,这是负责缓存的类.
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock;
默认情况下key
为url
.
首先通过key
在内存中寻找是否有图片缓存.
SDImageCache
持有SDMemoryCache
类型的memCache
, 用于内存缓存.SDMemoryCache
继承自NSCache
(NSCache(后续添加)).
UIImage *image = [self imageFromMemoryCacheForKey:key];
如果图片存在,并且策略也是,那就直接doneBlock
回调.
如果没有内存缓存, 创建异步操作,进行磁盘查询.在磁盘通过key
值MD5
加密,进行获取NSData
文件,进行解码,获取UIImage
对象.同时将UIImage
对象放入内存缓存中.
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
SDImageCacheType cacheType = SDImageCacheTypeDisk;
if (image) {
// the image is from in-memory cache
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData options:options];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
}
最后将操作返回赋值给SDWebImageCombinedOperation
的cacheOperation
.
SDWebImageDownloader
如果缓存没有就进入SDWebImageDownloader
这个类中, 进行图片下载操作
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
此方法中进行图片下载.
下载完成之后,进行缓存操作.
如果是错误url
,加入错误列表中.
如果下载成功,进行内存缓存,然后将图片进行编码成NSData
文件,存储到磁盘.
最后进行图片显示.
这是SDWebImage
的一个大致流程.
一些问题
-
SDWebImage
图片类型识别问题
在NSData+ImageContentType
中,根据文件头可以获取文件类型.case 0xFF: return SDImageFormatJPEG; case 0x89: return SDImageFormatPNG; case 0x47: return SDImageFormatGIF; case 0x49: case 0x4D: return SDImageFormatTIFF; case 0x52: { if (data.length >= 12) { //RIFF....WEBP NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding]; if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) { return SDImageFormatWebP; } } break; } case 0x00: { if (data.length >= 12) { //....ftypheic ....ftypheix ....ftyphevc ....ftyphevx NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding]; if ([testString isEqualToString:@"ftypheic"] || [testString isEqualToString:@"ftypheix"] || [testString isEqualToString:@"ftyphevc"] || [testString isEqualToString:@"ftyphevx"]) { return SDImageFormatHEIC; } if ([testString isEqualToString:@"ftypmif1"] || [testString isEqualToString:@"ftypmsf1"]) { return SDImageFormatHEIF; } } break; }
图片类型识别(后续添加)
-
异步加载多张图片时,SDWebImage是怎么做的? 队列执行方式?超时操作?
SDWebImageDownloader类持有多个共有属性://最大并发数, 默认为6 @property (assign, nonatomic) NSInteger maxConcurrentDownloads; //任务超时时长, 默认15s @property (assign, nonatomic) NSTimeInterval downloadTimeout; //队列执行方式, 默认先进先出 @property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
-
图片为什么用MD5编码的URL作为文件名?
MD5算法具有以下特点:
1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2、容易计算:从原数据计算出MD5值很容易。
3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4、强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。 -
缓存时长是多少?
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
-
清除特定图片缓存?
- 使用options:SDWebImageRefreshCached刷新缓存,但是反应该方法有闪烁问题,甚至有时并没有更新图片,所以保险起见,最好还是手动清缓存的方式。
- 每次清除掉图片缓存,重新加载的方式:
NSURL *imageURL = [NSURL URLWithString:@"http://upload-images.jianshu.io/upload_images/949086-5d2c51f1e3a9cddd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/999"]; // 获取对应URL链接的key NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:imageURL]; NSString *pathStr = [[SDImageCache sharedImageCache] defaultCachePathForKey:key]; NSLog(@"key存储的路径: %@", pathStr); // 删除对应key的文件 [[SDImageCache sharedImageCache] removeImageForKey:key withCompletion:^{ [self.tempImageView sd_setImageWithURL:imageURL placeholderImage:[UIImage imageNamed:@"placeholderHead.png"]]; }];
-
清除过期文件的时机
清除过期旧文件的时间点有两处:程序切到后台、杀死APP时。[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deleteOldFiles) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundDeleteOldFiles) name:UIApplicationDidEnterBackgroundNotification object:nil];