YYCache 源码学习(一)—— YYCache

系列其他文章:

  1. YYCache 源码学习(一)—— YYCache
  2. YYCache 源码学习(二)—— YYMemoryCache
  3. YYCache 源码学习(三)—— YYDiskCache

YYCacheibireme 开发的一套高性能的缓存框架,是 YYKit 的组件之一。

ibireme 介绍 YYCache 有如下特性:

  • LRU:缓存支持 LRU(least-recently-used)淘汰算法。
  • 缓存控制:支持多种缓存控制方法:总数量、总大小、存活时间、空闲时间。
  • 兼容性:API 基本和 NSCache 保持一致,所以方法都是线程安全的。
  • 内存缓存
    • 对象释放控制:对象的释放可以配置为同步或异步进行,可以配置在主线程或后台线程进行。
    • 自动清空:当收到内存警告或 App 进入后台时,缓存可以配置为自动清空。
  • 磁盘缓存
    • 可定制:磁盘缓存支持自定义的归档解档方法,以支持那些没有实现 NSCoding 协议的对象。
    • 存储类型控制:磁盘缓存支持对每个对象的存储类型(SQLite/文件)进行自动或手动控制,以获得更高的存取性能。

YYCache 框架的文件结构如下:

1
2
3
4
5
6
7
8
├── YYCache.h
├── YYCache.m
├── YYDiskCache.h
├── YYDiskCache.m
├── YYKVStorage.h
├── YYKVStorage.m
├── YYMemoryCache.h
└── YYMemoryCache.m

通常一个缓存是由内存缓存和磁盘缓存组成,内存缓存提供容量小但高速的存取功能,磁盘缓存提供大容量但低速的持久化存储。

YYCache

初始化方法

YYCache 一共提供了四个初始化的方法:

  1. - (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
  2. - (nullable instancetype)initWithName:(NSString *)name;
  3. + (nullable instancetype)cacheWithName:(NSString *)name;
  4. + (nullable instancetype)cacheWithPath:(NSString *)path;

可以看到方法 - initWithPath: 被标记为 NS_DESIGNATED_INITIALIZER,所以另外三个初始化方法最终都会调用 - initWithPath: 来初始化。此外,- init+ new 都被标记为 UNAVAILABLE_ATTRIBUTE,以防止使用者的错误使用。

+ cacheWithName:+ cacheWithPath: 这两个类方法都是直接调用对应的实例方法 - initWithName:- initWithPath:

- initWithName: 则是通过传入的 name 来生产一个对应的文件路径,然后交由 - initWithPath: 来初始化。生产路径的代码如下:

1
2
NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
NSString *path = [cacheFolder stringByAppendingPathComponent:name];

由代码可知,文件路径在 App 沙盒的 Library/Caches 下。可以说 Library/Caches 是 YYCache 默认使用的磁盘缓存存储路径,同时也是符合  对 iOS App 沙盒各文件夹的定义的:

  • Documents:这个目录用语存储用户数据。该路径可通过配置实现 iTunes 文件共享。可被 iTunes 备份。
  • Library
    • Preferences:保含 App 的偏好设置文件。不应该直接创建偏好配置文件,而是应该使用 NSUserDefaults 类来取得和设置 App 的偏好。
    • Caches:用于存放应用程序专用的支持文件,保存应用程序再次启动过程中需要的信息。可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份。
  • tmp:这个目录用于存放临时文件,保存应用程序再次启动过程中不需要的信息。该路径下的文件不会被iTunes备份。

- initWithPath: 内对 YYCache 的属性进行创建和赋值。YYDiskCache 通过 path 进初始化,同时将 path 的最好一个路径作为 YYMemoryCacheYYCache 的名称。

根据 key 查询是否存在缓存

  • - (BOOL)containsObjectForKey:(NSString *)key;
  • - (void)containsObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, BOOL contains))block;

这两个方法都会在磁盘缓存和内存缓存中查询对应 key 的缓存是否存在,若存在返回 YES,否则返回 NO,会先查询内存缓冲,若内存缓存中没有对应的缓存,才会去查询磁盘缓存,也就是说,如果在内存缓存中查询到了,则不会执行磁盘缓存查询的操作。

不同的是,- containsObjectForKey: 会在查询的时候阻塞调用的线程,如果在主线程中调用了该方法,很可能导致 UI 卡顿,而 - containsObjectForKey: withBlock: 则不会阻塞调用的线程。如果是在内存缓存中查询到对应的缓存,则会在 DISPATCH_QUEUE_PRIORITY_DEFAULTglobal queue 中调用 block,若是在磁盘缓存中查询到对应的缓存,则是在 YYDisckCache 内部创建的并行队列中执行,无论在哪个缓存中查询到对应的缓存,如果需要执行 UI 相关的操作,都要切换到主线程再操作。

此外,在 - containsObjectForKey: withBlock: 如果传如的 block 参数为空,则该方法会立马返回,不会执行任何查询操作,避免了不必要的性能开销。

根据 key 读取缓存

  • - (nullable id<NSCoding>)objectForKey:(NSString *)key;
  • - (void)objectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, id<NSCoding> object))block;

与缓存的检测类似,缓存的读取也有两个方法,一个会阻塞调用的线程,一个通过 block 回调来避免线程阻塞。

会先从内存缓存中读取对应的缓存,若内存缓存中不存在,则再去磁盘缓存中读取,如果磁盘缓存中有对应的缓存,不仅会被该缓存返回,还会将其写入到内存缓存中。

- objectForKey: withBlock: 中,如果是在内存缓存中查询到对应的缓存,则会在 DISPATCH_QUEUE_PRIORITY_DEFAULTglobal queue 中调用 block,若是在磁盘缓存中查询到对应的缓存,则是在 YYDisckCache 内部创建的并行队列中执行,无论在哪个缓存中查询到对应的缓存,如果需要执行 UI 相关的操作,都要切换到主线程再操作。

此外,在 - objectForKey: withBlock: 如果传如的 block 参数为空,则该方法会立马返回,不会执行任何查询操作,避免了不必要的性能开销。

根据 key 设置缓存

  • - (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;
  • - (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(nullable void(^)(void))block;

方法 - setObject: forKey: 会在缓存写入完成后返回,所以可能阻塞线程;方法 setObject: forKey: withBlock: 则会立马返回,而在缓存写入完成后,调用 block(block 的调用和缓存的查询,读取类似),block 主要是在写入磁盘完成后被调用。

根据 key 移除缓存

  • - (void)removeObjectForKey:(NSString *)key;
  • - (void)removeObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key))block;

方法 - removeObjectForKey: 会在缓存删除完成后返回,所以可能阻塞线程;方法 removeObjectForKey: withBlock: 则会立马返回,而在缓存删除完成后,调用 block(block 的调用和缓存的查询,读取类似),block 主要是在从磁盘删除完成后被调用。

移除所有缓存

  • - (void)removeAllObjects;
  • - (void)removeAllObjectsWithBlock:(void(^)(void))block;
  • - (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress endBlock:(nullable void(^)(BOOL error))end;

方法 - removeAllObjects 会在缓存全部删除完成后返回,所以可能阻塞线程;方法 - removeAllObjectsWithBlock: 则会立马返回,而在缓存全部删除完成后,调用 block(block 的调用和缓存的查询,读取类似),block 主要是在从磁盘缓存全部删除完成后被调用。方法 - removeAllObjectsWithProgressBlock: endBlock: 除了提供全部缓存删除完成是的回调以外,还提供了缓存进度的提高,方便了 UI 上的进度条显示等实现,调用 block(block 的调用和缓存的查询,读取类似)。

相对与内存缓存而已,对磁盘的缓存往往比较耗时,耗能,所以 YYDisckCache 提高了不阻塞线程的各个方法,也因为如此,所以很多回调,进度等都是以磁盘缓存的操作为基准,将内存缓存的操作耗时忽略。在读取,查询的时候,也都是先对内存缓存进行操作,以缩短相应的时间,节省性能开销。

相关链接

0%