在看完 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 部分的全部代碼就已經解讀完畢了。