SDWebImage学习笔记之SDImageCache

SDMemoryCache

SDMemoryCache是SDImageCache类中的一个私有类,继承自NSCache类,它接收两个泛型<KeyType, ObjectType>用于定义NSMapTable类型的属性weakCache。

// strong-weak cache
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; 

NSMapTable在SDWebImage学习笔记之NSMapTable中做过介绍,weakCache属性的初始化代码为

self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];

表示weakCache变量指向的对象强引用key值,弱引用value值。假如没有其他变量强引用value值,weakCache变量将安全的删除相应的key-value。

SDMemoryCache还定义了一个dispatch_semaphore_t属性weakCacheLock。

// a lock to keep the access to `weakCache` thread-safe
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; 

weakCacheLock属性创建了一个初始值为1的信号量,表示同时最多只有一个线程可以访问资源,初始化代码为:

self.weakCacheLock = dispatch_semaphore_create(1);

SDMemoryCache重写了父类NSCache的三个方法:

  1. -(nullable ObjectType)objectForKey:(KeyType)key;
  2. -(void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
  3. -(void)removeObjectForKey:(KeyType)key;

通过对信号量weakCacheLock的控制,实现线程安全的weakCache赋值、取值、删除操作,还提供了一个清空缓存的函数
-(void)removeAllObjects。

#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#define UNLOCK(lock) dispatch_semaphore_signal(lock);

FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
    return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
    return image.size.height * image.size.width * image.scale * image.scale;
#endif

// Store weak cache
LOCK(self.weakCacheLock);
[self.weakCache setObject:obj forKey:key];
UNLOCK(self.weakCacheLock);

// Check weak cache
LOCK(self.weakCacheLock);
obj = [self.weakCache objectForKey:key];
UNLOCK(self.weakCacheLock);
if (obj) {
    // Sync cache
    NSUInteger cost = 0;
    if ([obj isKindOfClass:[UIImage class]]) {
        cost = SDCacheCostForImage(obj);
    }
    [super setObject:obj forKey:key cost:cost];
}

// Remove weak cache
LOCK(self.weakCacheLock);
[self.weakCache removeObjectForKey:key];
UNLOCK(self.weakCacheLock);

// Manually remove should also remove weak cache
LOCK(self.weakCacheLock);
[self.weakCache removeAllObjects];
UNLOCK(self.weakCacheLock);

SDMemoryCache小结

SDMemoryCache类的两个属性:weakCache和weakCacheLock。weakCache用于保存数据,且当数据在外部被销毁时,weakCache可以安全的清除对应的键值对;weakCacheLock用于保证线程安全,同一时刻只允许只允许有一个线程对weakCache进行读写。


SDImageCacheConfig

SDImageCacheConfig用于SDImageCache的配置,继承自NSObject,定义配置属性如下:

// 是否解压图片
@property (assign, nonatomic) BOOL shouldDecompressImages;
// 是否禁用iCloud
@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;
// 缓存配置过期类型,枚举
@property (assign, nonatomic) SDImageCacheConfigExpireType diskCacheExpireType;

SDImageCache

SDImageCache是Cache模块的核心类,它提供了一系列的方法来存储图片,以其中最重要的存储方法为例:

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;

该方法共有6个参数,image表示图片,imageData表示图片数据,在赋值时,代码先判断imageData是否为空,不为空则直接将NSData类型的数据进行存储,如果imageData为空且image不为空,则先判断UIImage类型的图片是否存在Alpha(透明)通道,返回一个SDImageFormat类型的枚举值用于将UIImage转化为NSData,然后进行存储。

// 及时释放图片资源
@autoreleasepool {
    NSData *data = imageData;
    // 判断imageData是否为空
    if (!data && image) {
        // 判断images是否存在Alpha Channel
        SDImageFormat format;
        if (SDCGImageRefContainsAlpha(image.CGImage)) {
            format = SDImageFormatPNG;
        } else {
            format = SDImageFormatJPEG;
        }
        // UIImage -> NSData
        data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
    }
    // 存储data
    [self _storeImageDataToDisk:data forKey:key];
}

第三个参数key有两个作用,一是在内存缓存可用的前提下作为key存储图片,形成映射关系;二是生成磁盘缓存的URL地址,将图片保存在该地址下,后期可以通过key获取地址。

1.
// 内存缓存可用
if (self.config.shouldCacheImagesInMemory) {
    // 计算图片占用空间大小
    NSUInteger cost = SDCacheCostForImage(image);
    // 将key和image作为键值对存储在NSMapTable类型的内存缓存中
    [self.memCache setObject:image forKey:key cost:cost];
}

2.
// _storeImageDataToDisk:forkey:方法
// 使用key生成磁盘缓存地址
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// 将地址转换成NSURL对象
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
// 存储图片
[imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];

第四个参数toDisk用于标示是否需要将图片存储到磁盘,需要的话才会执行存储的操作,第五个参数completionBlock是执行结束的回调,返回值为空。

if (toDisk) {
    // 开启新的线程执行
    dispatch_async(self.ioQueue, ^{
        // 将图片缓存到磁盘
        ......
            
        if (completionBlock) {
            // 返回主线程执行
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
} else {
    if (completionBlock) {
        completionBlock();
    }
}

SDImageCache类中定义了属性ioQueue,它创建了一个串行队列。

_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

串行队列在执行异步操作时,会开启一个新的线程来执行,具体可参考下表:

同步 异步
串行队列(主队列) 在主线程中执行 在主线程中执行
串行队列(非主队列) 在当前线程中执行 在新建线程中执行
并发队列 在当前线程中执行 在新建线程中执行

因此在子线程完成存储图片的操作后,需返回到主线程队列执行回调函数。

SDImageCache还提供了查询图片是否存在于磁盘、获取磁盘图片、查询内存图片、删除磁盘图片、删除内存图片等方法,同样,所有的方法都会在串行队列中异步执行,由于串行队列遵守FIFO(先进先出)的原则,所以可以保证只有才一个操作完成后,下一个方法才可以被执行。

当从硬盘中获取图片时,代码会判断内存缓存是否可用,如果可用,则将图片缓存到内存中。

// 获取图片
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
    UIImage *diskImage = [self diskImageForKey:key];
    // 判断图片是否存在及内存缓存是否可用
    if (diskImage && self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(diskImage);
        [self.memCache setObject:diskImage forKey:key cost:cost];
    }
    return diskImage;
}

SDImageCache中定义了一个属性customPaths,可以调用addReadOnlyCachePath:方法往customPaths数组中添加常用的磁盘缓存路径,以便diskImageDataBySearchingAllPathsForKey:方法查找图片资源。

- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
    ......
    NSArray<NSString *> *customPaths = [self.customPaths copy];
    for (NSString *path in customPaths) {
        ......
    }
    ......
}

除了对图片的操作之外,SDImageCache还提供了内存缓存的设置功能,包括设置缓存大小和数量限制,还提供了清理内存缓存、清理磁盘缓存、删除文件的方法,还有一些其他获取图片信息、缓存信息的方法等等。


总结

SDImageCache的核心功能是对图片的存储、查找、删除操作。提供了两种方法用于缓存图片,内存缓存和磁盘缓存。

内存指的是程序的运行空间,空间小但缓存速度快,程序一关闭,内存就被释放了,内存分5大区域:栈区、堆区、全局区、常量区、代码区,由高地址指向低地址。

磁盘指的是程序的存储空间,空间大但缓存速度慢,数据可持久化。
iOS程序的磁盘被称为沙盒,本程序无法访问其他应用的沙盒,沙盒中默认有3个文件夹:Documents, Library 和 tmp。

而且所有的操作都是在串行队列中异步执行,即不会阻塞主线程,也保证了数据的安全性。

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

推荐阅读更多精彩内容