SDWebImage 4.x版本源码分析(四)SDWebImageCache

可以来这里下载一下源码注释

5.SDWebImageCache

问题:

①.SDImageCache是怎么存储内存缓存和磁盘缓存的?
②.NSCache 是什么?
③.磁盘缓存的路径是什么?
④.如何清理缓存?何时会自动清理缓存?
⑤.图片解压的作用是什么?
⑥.为什么要用NSMapTable?

枚举

typedef NS_ENUM(NSInteger, SDImageCacheType) {
//没有缓存
    SDImageCacheTypeNone,
//磁盘缓存
    SDImageCacheTypeDisk,
//内存缓存
    SDImageCacheTypeMemory
};

typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
   //当图片缓存到内存中时,强制查询磁盘数据
    SDImageCacheQueryDataWhenInMemory = 1 << 0,
   //默认情况下,我们同步查询内存缓存,异步地查询磁盘缓存。此掩码可以同步查询磁盘缓存。
    SDImageCacheQueryDiskSync = 1 << 1
};

.h中的属性

@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;

//缓存应该持有的对象的最大数量。
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;

.m中的属性

@property (strong, nonatomic, nonnull) SDMemoryCache *memCache;
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue;
@property (strong, nonatomic, nonnull) NSFileManager *fileManager;

.h中的方法

//init
+ (nonnull instancetype)sharedImageCache;
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;

//缓存路径
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;
- (void)addReadOnlyCachePath:(nonnull NSString *)path;

//缓存操作
- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;

//查询和检索操作
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key;
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock;
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key;
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;

//删除操作
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;

//缓存清理
- (void)clearMemory;
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;

//缓存配置
- (NSUInteger)getSize;
- (NSUInteger)getDiskCount;
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock;

//缓存路径
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path;
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key;

.m中的方法

//init dealloc
+ (nonnull instancetype)sharedImageCache
- (instancetype)init 
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory
- (void)dealloc

//缓存路径
- (void)addReadOnlyCachePath:(nonnull NSString *)path
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key 
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace

//缓存操作
- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
        completion:(nullable SDWebImageNoParamsBlock)completionBlock
- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock
- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key

//查询和检索操作
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock
- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key
- (BOOL)_diskImageDataExistsWithKey:(nullable NSString *)key
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image
- (NSOperation *)queryCacheOperationForKey:(NSString *)key done:(SDCacheQueryCompletedBlock)doneBlock
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock

//删除操作
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion

//内存缓存设置
- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost
- (NSUInteger)maxMemoryCost
- (NSUInteger)maxMemoryCountLimit
- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit

//缓存清理
- (void)clearMemory
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion
- (void)deleteOldFiles
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock

//缓存配置
- (NSUInteger)getSize
- (NSUInteger)getDiskCount
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock

SDWebImageManager中直接调用的是 queryCacheOperationForKey这个方法,按照方法调用的顺序来看一下这里的实现:

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock
{
//1.先检查内存缓存, 如果图片在内存中,并且没有强制要求查询磁盘,就返回
//2.创建NSOperation对象
//3.如果NSOperation对象取消了,就return,否则继续
//4.开启异步队列,读取磁盘缓存,
//5.如果有磁盘数据,就解码图像数据
//6.如果解码成功,并且需要缓存到内存中,就添加到内存中
//7.主线程中回调
//8.return operation
}

关于查找磁盘缓存中的图片

- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key
{
//1. defaultCachePathForKey  读取磁盘缓存(沙盒)
//2. 根据路径读取data,如果找到就返回data
//3. 如果没有找到,去掉扩展名再试下
//4. 如果还是没有,就读取bundle中的数据 (addReadOnlyCachePath 获取到的那个)
}

关于存储图片的路径

- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
    // 经过 MD5 处理的文件名
    NSString *filename = [self cachedFileNameForKey:key];
    // path/<#MD5_filename#>
    return [path stringByAppendingPathComponent:filename];
}
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
    //图片存储的路径为 Libray/Cache/<#namespace#>/com.hackemist.SDWebImageCache.<#namespace#>/<#MD5_filename#>
    return [self cachePathForKey:key inPath:self.diskCachePath];
}
- (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);
    NSURL *keyURL = [NSURL URLWithString:key];
    NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
    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], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
    return filename;
}
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
    // 获取cache目录路径
    NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

关于自动清理磁盘缓存
缓存的配置4.x版本放到了SDImageCacheConfig 里
SDImageCacheConfig

.h中的属性
//解压缩图像下载和缓存可以提高性能,但是会消耗大量内存, 默认是YES ,如果因为消耗内存过大而崩溃,可以置为NO。
//是否解压图片,默认YES
@property (assign, nonatomic) BOOL shouldDecompressImages;
//是否禁用 iCloud 备份,默认是 YES
@property (assign, nonatomic) BOOL shouldDisableiCloud;
//是否缓存到内存中,默认是YES
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
//读取磁盘缓存时的阅读选项。
@property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions;
//在将缓存写入磁盘时写入选项。
@property (assign, nonatomic) NSDataWritingOptions diskCacheWritingOptions;
//在缓存中保存图像的最长时间,以秒为单位。(默认一周)
@property (assign, nonatomic) NSInteger maxCacheAge;
//缓存的最大大小,以字节为单位。
@property (assign, nonatomic) NSUInteger maxCacheSize;

.m

#import "SDImageCacheConfig.h"

static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 一周,默认超过7天的图片清除

@implementation SDImageCacheConfig

- (instancetype)init {
    if (self = [super init]) {
        _shouldDecompressImages = YES;
        _shouldDisableiCloud = YES;
        _shouldCacheImagesInMemory = YES;
        _diskCacheReadingOptions = 0;
        _diskCacheWritingOptions = NSDataWritingAtomic;
        _maxCacheAge = kDefaultCacheMaxCacheAge;
        _maxCacheSize = 0;
    }
    return self;
}
@end

在SDImageCache里,内存缓存是放在 SDMemoryCache中,在SDMemoryCache中,当内存警告时,会删掉内存缓存
SDMemoryCache继承于NSCache,所以在init方法里添加了观察者

SDMemoryCache的 implementation

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
- (instancetype)init {
    self = [super init];
    if (self) {
        // Use a strong-weak maptable storing the secondary cache. Follow the doc that NSCache does not copy keys
        // This is useful when the memory warning, the cache was purged. However, the image instance can be retained by other instance such as imageViews and alive.
        // At this case, we can sync weak cache back and do not need to load from disk cache
        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];
    }
    return self;
}

- (void)didReceiveMemoryWarning:(NSNotification *)notification {
    // Only remove cache, but keep weak cache
    [super removeAllObjects];
}

磁盘缓存的清理在SDImageCache中监听

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory
{
……

        //当程序将要终止时,异步删掉旧的文件
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(deleteOldFiles)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];
        //将要进去后台时,在后台异步删掉旧的文件
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];

……

}

关于清理磁盘缓存的操作

- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock
{
1.创建异步操作
2.获取缓存文件目录
3. 计算过期日期
4.清理图片
4.1清理过期图片
4.2清理超过最大缓存的图片
}

查询缓存流程图:

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容