在看完 Manager 部分的代码后,咱们来看下 Cache 部分的代码。SDWebImageManager 中使用的是 SDImageCache 这个类,因而咱们就从这个类入手来看一下 SDWebImage 是如何设计它的缓存系统的。git
SDImageCache 有两部分组成,一个是内存缓存 memoryCache,另外一个是磁盘缓存 diskCache,先来看它的初始化方法。github
在最基础的使用方法中,SDImageCache 是当作单例来使用的,namespace 使用 default,在指定初始化方法中,初始化了 ioQueue,并根据 SDImageCacheConfig 中的默认配置提供的类名分别初始化 memoryCache 和 diskCache。缓存
+ (nonnull instancetype)sharedImageCache {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (instancetype)init {
return [self initWithNamespace:@"default"];
}
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
return [self initWithNamespace:ns diskCacheDirectory:nil];
}
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nullable NSString *)directory {
return [self initWithNamespace:ns diskCacheDirectory:directory config:SDImageCacheConfig.defaultCacheConfig];
}
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nullable NSString *)directory
config:(nullable SDImageCacheConfig *)config {
if ((self = [super init])) {
NSAssert(ns, @"Cache namespace should not be nil");
// Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDImageCache", DISPATCH_QUEUE_SERIAL);
if (!config) { // 若是没有自定义的配置,则使用 SDImageCacheConfig.defaultCacheConfig 做为默认配置
config = SDImageCacheConfig.defaultCacheConfig;
}
_config = [config copy];
// Init the memory cache
NSAssert([config.memoryCacheClass conformsToProtocol:@protocol(SDMemoryCache)], @"Custom memory cache class must conform to `SDMemoryCache` protocol");
_memoryCache = [[config.memoryCacheClass alloc] initWithConfig:_config];
// Init the disk cache
if (directory != nil) {
_diskCachePath = [directory stringByAppendingPathComponent:ns];
} else {
NSString *path = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:ns];
_diskCachePath = path;
}
NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)], @"Custom disk cache class must conform to `SDDiskCache` protocol");
_diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config];
// Check and migrate disk cache directory if need
[self migrateDiskCacheDirectory];
#if SD_UIKIT
// Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
#if SD_MAC
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:NSApplicationWillTerminateNotification
object:nil];
#endif
}
return self;
}
复制代码
SDWebImageCacheConfig 是配置里,里面包含了对 SDWebImageCache 的所有配置,看一下它的初始化方法了解下默认的配置都是什么。app
- (instancetype)init {
if (self = [super init]) {
_shouldDisableiCloud = YES; // 默认禁用 iCloud
_shouldCacheImagesInMemory = YES; // 在内存中存储图片
_shouldUseWeakMemoryCache = YES; // 使用 weakMemoryCache
_shouldRemoveExpiredDataWhenEnterBackground = YES; // 进入后台时清除过时缓存
_diskCacheReadingOptions = 0; // 使用默认读配置
_diskCacheWritingOptions = NSDataWritingAtomic; // 使用原子写配置
_maxDiskAge = kDefaultCacheMaxDiskAge; // 最大过时时间为 static const NSInteger kDefaultCacheMaxDiskAge = 60 * 60 * 24 * 7; // 1 week
_maxDiskSize = 0; // 没有最大磁盘限制
_diskCacheExpireType = SDImageCacheConfigExpireTypeModificationDate; // 根据图片的修改日期做为过时时间的判断标准
_memoryCacheClass = [SDMemoryCache class]; // 使用 SDMemoryCache 做为内存缓存类
_diskCacheClass = [SDDiskCache class]; // 使用 SDDiskCache 做为磁盘缓存类
}
return self;
}
复制代码
SDMemoryCache 是内存缓存类,其声明以下:async
/** A memory cache which auto purge the cache on memory warning and support weak cache. */
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType> <SDMemoryCache>
@property (nonatomic, strong, nonnull, readonly) SDImageCacheConfig *config;
@end
复制代码
SDMemoryCache 是泛型类,且是 NSCache 的子类,初始化方法以下:ide
- (instancetype)init {
self = [super init];
if (self) {
_config = [[SDImageCacheConfig alloc] init];
[self commonInit];
}
return self;
}
- (instancetype)initWithConfig:(SDImageCacheConfig *)config {
self = [super init];
if (self) {
_config = config;
[self commonInit];
}
return self;
}
- (void)commonInit {
SDImageCacheConfig *config = self.config;
self.totalCostLimit = config.maxMemoryCost;
self.countLimit = config.maxMemoryCount;
[config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) options:0 context:SDMemoryCacheContext];
[config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) options:0 context:SDMemoryCacheContext];
#if SD_UIKIT
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
self.weakCacheLock = dispatch_semaphore_create(1);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
#endif
}
复制代码
咱们能够看到在 SD_UIKIT 宏中,初始化了 weakCache,这也是上文中提到的配置项,其中 key 以 strong 的方式存储,value 以 weak 的方式存储。初始化方法里还监听了 UIApplicationDidReceiveMemoryWarningNotification 通知和 maxMemoryCost & maxMemoryCount KVO,处理代码以下所示:函数
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
// Only remove cache, but keep weak cache
[super removeAllObjects];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == SDMemoryCacheContext) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCost))]) {
self.totalCostLimit = self.config.maxMemoryCost;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCount))]) {
self.countLimit = self.config.maxMemoryCount;
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
复制代码
同时,在 SD_UIKit 环境下,SDMemoryCache 经过 weakMemoryCache 实现了两套记录缓存的工具,防止由于内存紧张频繁释放内存致使频繁从磁盘加载图片。工具
// `setObject:forKey:` just call this with 0 cost. Override this is enough
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
[super setObject:obj forKey:key cost:g];
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
if (key && obj) {
// Store weak cache
SD_LOCK(self.weakCacheLock);
[self.weakCache setObject:obj forKey:key];
SD_UNLOCK(self.weakCacheLock);
}
}
- (id)objectForKey:(id)key {
id obj = [super objectForKey:key];
if (!self.config.shouldUseWeakMemoryCache) {
return obj;
}
if (key && !obj) {
// Check weak cache
SD_LOCK(self.weakCacheLock);
obj = [self.weakCache objectForKey:key];
SD_UNLOCK(self.weakCacheLock);
if (obj) {
// Sync cache
NSUInteger cost = 0;
if ([obj isKindOfClass:[UIImage class]]) {
cost = [(UIImage *)obj sd_memoryCost];
}
[super setObject:obj forKey:key cost:cost];
}
}
return obj;
}
- (void)removeObjectForKey:(id)key {
[super removeObjectForKey:key];
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
if (key) {
// Remove weak cache
SD_LOCK(self.weakCacheLock);
[self.weakCache removeObjectForKey:key];
SD_UNLOCK(self.weakCacheLock);
}
}
- (void)removeAllObjects {
[super removeAllObjects];
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
// Manually remove should also remove weak cache
SD_LOCK(self.weakCacheLock);
[self.weakCache removeAllObjects];
SD_UNLOCK(self.weakCacheLock);
}
复制代码
为了计算每个图片所需的 cost,SDWebImage 提供了分类方法来计算这个:ui
- (NSUInteger)sd_memoryCost {
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_memoryCost));
NSUInteger memoryCost;
if (value != nil) {
memoryCost = [value unsignedIntegerValue];
} else {
memoryCost = SDMemoryCacheCostForImage(self);
}
return memoryCost;
}
FOUNDATION_STATIC_INLINE NSUInteger SDMemoryCacheCostForImage(UIImage *image) { // 计算每一帧的每一行所需的 bytes 乘以总行数,而后乘以帧数来计算图片的 cost
CGImageRef imageRef = image.CGImage;
if (!imageRef) {
return 0;
}
NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef);
NSUInteger frameCount;
#if SD_MAC
frameCount = 1;
#elif SD_UIKIT || SD_WATCH
frameCount = image.images.count > 0 ? image.images.count : 1;
#endif
NSUInteger cost = bytesPerFrame * frameCount;
return cost;
}
复制代码
看完内存缓存以后,来看 SDWebImage 内置的磁盘缓存,是经过 SDDiskCache 这个类实现的,声明以下:this
/** The built-in disk cache. */
@interface SDDiskCache : NSObject <SDDiskCache>
/** Cache Config object - storing all kind of settings. */
@property (nonatomic, strong, readonly, nonnull) SDImageCacheConfig *config;
- (nonnull instancetype)init NS_UNAVAILABLE;
/** Move the cache directory from old location to new location, the old location will be removed after finish. If the old location does not exist, does nothing. If the new location does not exist, only do a movement of directory. If the new location does exist, will move and merge the files from old location. If the new location does exist, but is not a directory, will remove it and do a movement of directory. @param srcPath old location of cache directory @param dstPath new location of cache directory */
- (void)moveCacheDirectoryFromPath:(nonnull NSString *)srcPath toPath:(nonnull NSString *)dstPath;
@end
复制代码
主要的接口都声明在 SDDiskCache 协议里了,SDDiskCache 的初始化方法也很简单,只初始化了 fileManager:
- (instancetype)init {
NSAssert(NO, @"Use `initWithCachePath:` with the disk cache path");
return nil;
}
#pragma mark - SDcachePathForKeyDiskCache Protocol
- (instancetype)initWithCachePath:(NSString *)cachePath config:(nonnull SDImageCacheConfig *)config {
if (self = [super init]) {
_diskCachePath = cachePath;
_config = config;
[self commonInit];
}
return self;
}
- (void)commonInit {
if (self.config.fileManager) {
self.fileManager = self.config.fileManager;
} else {
self.fileManager = [NSFileManager new];
}
}
复制代码
其余 SDDiskCache 的方法咱们稍后遇到了再讲解。
该方法就是在第一次初始化这个类的时候作一次数据迁移,旧地址和新地址的区别在于加了 com.hackemist.SDImageCache,防止出现冲突。
- (void)migrateDiskCacheDirectory {
if ([self.diskCache isKindOfClass:[SDDiskCache class]]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// ~/Library/Caches/com.hackemist.SDImageCache/default/
NSString *newDefaultPath = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:@"default"];
// ~/Library/Caches/default/com.hackemist.SDWebImageCache.default/
NSString *oldDefaultPath = [[[self userCacheDirectory] stringByAppendingPathComponent:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"];
dispatch_async(self.ioQueue, ^{
[((SDDiskCache *)self.diskCache) moveCacheDirectoryFromPath:oldDefaultPath toPath:newDefaultPath];
});
});
}
}
复制代码
SDDiskCache 的 moveCacheDirectoryFromPath:toPath: 方法实现以下:
- (void)moveCacheDirectoryFromPath:(nonnull NSString *)srcPath toPath:(nonnull NSString *)dstPath {
NSParameterAssert(srcPath);
NSParameterAssert(dstPath);
// Check if old path is equal to new path
if ([srcPath isEqualToString:dstPath]) {
return;
}
BOOL isDirectory;
// Check if old path is directory
if (![self.fileManager fileExistsAtPath:srcPath isDirectory:&isDirectory] || !isDirectory) {
return;
}
// Check if new path is directory
if (![self.fileManager fileExistsAtPath:dstPath isDirectory:&isDirectory] || !isDirectory) {
if (!isDirectory) {
// New path is not directory, remove file
[self.fileManager removeItemAtPath:dstPath error:nil];
}
NSString *dstParentPath = [dstPath stringByDeletingLastPathComponent];
// Creates any non-existent parent directories as part of creating the directory in path
if (![self.fileManager fileExistsAtPath:dstParentPath]) {
[self.fileManager createDirectoryAtPath:dstParentPath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// New directory does not exist, rename directory
[self.fileManager moveItemAtPath:srcPath toPath:dstPath error:nil];
} else {
// New directory exist, merge the files
NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtPath:srcPath];
NSString *file;
while ((file = [dirEnumerator nextObject])) {
[self.fileManager moveItemAtPath:[srcPath stringByAppendingPathComponent:file] toPath:[dstPath stringByAppendingPathComponent:file] error:nil];
}
// Remove the old path
[self.fileManager removeItemAtPath:srcPath error:nil];
}
}
复制代码
从缓存中加载图片是经过 queryImageForKey:options:context:completion: 方法进行的,实现以下:
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData; // 是否查询内存缓存
if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync; // 是否同步查询内存缓存
if (options & SDWebImageQueryDiskDataSync) cacheOptions |= SDImageCacheQueryDiskDataSync; // 是否同步查询磁盘缓存
if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages; // 是否须要下降大图分辨率
if (options & SDWebImageAvoidDecodeImage) cacheOptions |= SDImageCacheAvoidDecodeImage; // 是否避免解码图片
if (options & SDWebImageDecodeFirstFrameOnly) cacheOptions |= SDImageCacheDecodeFirstFrameOnly; // 是否只解码第一帧
if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames; // 是否提早预解码全部帧
return [self queryCacheOperationForKey:key options:cacheOptions context:context done:completionBlock];
}
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
if (transformer) {
// grab the transformed disk image if transformer provided
NSString *transformerKey = [transformer transformerKey];
key = SDTransformedKeyForKey(key, transformerKey);
}
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if ((options & SDImageCacheDecodeFirstFrameOnly) && image.sd_isAnimated) { // 只解码第一帧的状况
#if SD_MAC
image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
}
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
// Second check the disk cache...
NSOperation *operation = [NSOperation new];
// Check whether we need to synchronously query disk
// 1. in-memory cache hit & memoryDataSync
// 2. in-memory cache miss & diskDataSync
BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
(!image && options & SDImageCacheQueryDiskDataSync));
void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
SDImageCacheType cacheType = SDImageCacheTypeNone;
if (image) {
// the image is from in-memory cache, but need image data
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
cacheType = SDImageCacheTypeDisk;
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
if (shouldQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};
// Query in ioQueue to keep IO-safe
if (shouldQueryDiskSync) {
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
复制代码
让咱们拆解其中的关键部分,来分别讲解其实现:
SDTransformedKeyForKey 函数是用来经过 transformerKey 和传入的 key 结合起来生成新 key 的函数,其实现以下:
// Separator for different transformerKey, for example, `image.png` |> flip(YES,NO) |> rotate(pi/4,YES) => 'image-SDImageFlippingTransformer(1,0)-SDImageRotationTransformer(0.78539816339,1).png'
static NSString * const SDImageTransformerKeySeparator = @"-";
NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * _Nonnull transformerKey) {
if (!key || !transformerKey) {
return nil;
}
// Find the file extension
NSURL *keyURL = [NSURL URLWithString:key];
NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
if (ext.length > 0) {
// For non-file URL
if (keyURL && !keyURL.isFileURL) {
// keep anything except path (like URL query)
NSURLComponents *component = [NSURLComponents componentsWithURL:keyURL resolvingAgainstBaseURL:NO];
component.path = [[[component.path.stringByDeletingPathExtension stringByAppendingString:SDImageTransformerKeySeparator] stringByAppendingString:transformerKey] stringByAppendingPathExtension:ext];
return component.URL.absoluteString;
} else {
// file URL
return [[[key.stringByDeletingPathExtension stringByAppendingString:SDImageTransformerKeySeparator] stringByAppendingString:transformerKey] stringByAppendingPathExtension:ext];
}
} else {
return [[key stringByAppendingString:SDImageTransformerKeySeparator] stringByAppendingString:transformerKey];
}
}
复制代码
简而言之就是对 key 进行字符串的拼接
从磁盘中获取图片 data 的方法实现很简单,以下所示:
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
if (!key) {
return nil;
}
NSData *data = [self.diskCache dataForKey:key]; // 从 diskCache 中获取 data
if (data) {
return data;
}
// Addtional cache path for custom pre-load cache
if (self.additionalCachePathBlock) {
NSString *filePath = self.additionalCachePathBlock(key);
if (filePath) {
data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
}
}
return data;
}
复制代码
在从磁盘缓存中查询到图片数据后,会经过下面的方法将其转为 Image 对象。
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options context:(SDWebImageContext *)context {
if (data) {
UIImage *image = SDImageCacheDecodeImageData(data, key, [[self class] imageOptionsFromCacheOptions:options], context);
return image;
} else {
return nil;
}
}
复制代码
SDImageCacheDecodeImageData 函数实现以下:
UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
UIImage *image;
BOOL decodeFirstFrame = options & SDWebImageDecodeFirstFrameOnly; // 判断是否是只须要解码第一帧
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor]; // 获取图片解码 scale,
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
if (context) { // 若是 context 参数存在,配置 coderOptions
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
coderOptions = [mutableCoderOptions copy];
}
if (!decodeFirstFrame) { // 若是不单单解码第一帧,则从 context 中取出 AnimatedImageClass,默认使用的是 SDAnimatedImage,而后进行图片数据的解码,根据须要还能够预解码全部帧
Class animatedImageClass = context[SDWebImageContextAnimatedImageClass];
// check whether we should use `SDAnimatedImage`
if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)]) {
image = [[animatedImageClass alloc] initWithData:imageData scale:scale options:coderOptions];
if (options & SDWebImagePreloadAllFrames && [image respondsToSelector:@selector(preloadAllFrames)]) {
[((id<SDAnimatedImage>)image) preloadAllFrames];
}
}
}
if (!image) {
image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions]; // 生成图片
}
if (image) {
BOOL shouldDecode = (options & SDWebImageAvoidDecodeImage) == 0; // 查看是否须要解码
if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) { // 动图不须要解码
// `SDAnimatedImage` do not decode
shouldDecode = NO;
} else if (image.sd_isAnimated) {
// animated image do not decode
shouldDecode = NO;
}
if (shouldDecode) { // 解码操做
BOOL shouldScaleDown = options & SDWebImageScaleDownLargeImages;
if (shouldScaleDown) {
image = [SDImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0];
} else {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
}
}
return image;
}
复制代码
关于 Coder 部分的内容,未来会单独开一篇文章来说。
若是加载图片没有命中缓存,SDWebImageManager 会先去下载图片,而后下载的图片须要进行存储,存储的方法为 storeImage:imageData:forKey:cacheType:completion: 以下所示:
- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
switch (cacheType) {
case SDImageCacheTypeNone: {
[self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:NO completion:completionBlock];
}
break;
case SDImageCacheTypeMemory: {
[self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:NO completion:completionBlock];
}
break;
case SDImageCacheTypeDisk: {
[self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:YES completion:completionBlock];
}
break;
case SDImageCacheTypeAll: {
[self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:YES completion:completionBlock];
}
break;
default: {
if (completionBlock) {
completionBlock();
}
}
break;
}
}
复制代码
其内部调用了 storeImage:imageData:forKey:toMemory:toDisk:completion:,实现以下:
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toMemory:(BOOL)toMemory
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
if (toMemory && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = image.sd_memoryCost;
[self.memoryCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
SDImageFormat format;
if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil]; // 编码图片
}
[self _storeImageDataToDisk:data forKey:key]; // 存储图片到磁盘
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
复制代码
_storeImageDataToDisk:forKey: 方法的实现以下:
// Make sure to call form io queue by caller
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}
[self.diskCache setData:imageData forKey:key];
}
复制代码
至此,关于 cache 部分的全部代码就已经解读完毕了。