YYDiskCache
YYDiskCache 是一个基于SQLite和文件系统的线程安全的键值存储cache
YYDiskCache is a thread-safe cache that stores key-value pairs backed by SQLite
and file system (similar to NSURLCache's disk cache).
Feature
- YYDiskCache使用LRU算法去移除数据;
- YYDiskCache可以控制系统开销,缓存数量和缓存时间;
- YYDiskCache是可配置的,并可以在没有足够的磁盘空间的时候自动删除一些数据;
- YYDiskCache会自动帮我们决定每个数据是用sqlite存储或者是file存储更好一些;
我们也可以编译最新的sqlite版本来替换iOS系统的libsqlite3.dylib,这样的话,存取速度可以达到2-4倍的提升;
YYDiskCache里面的大多数实现都是依靠YYKVStorage,所以如果你看了YYKVStorage的话,你会发现他们有很多类似的API,在实际使用中,我们并不需要直接使用YYKVStorage,而是使用更高层封装的YYDiskCache就行了。
Attributes
attribute
-
name
: cache的名字,默认为nil; -
path
: 存储的路径(read-only); -
inlineThreshold
: 一个阈值;如果存储的数据size大于这个值,则以文件的形式存储,否则用sqlite存储;默认为20KB; -
NSData *(^customArchiveBlock)(id object)
: 如果此block不为空的话,那么就会用此block来archive数据而不是用默认的NSKeyedArchiver,这样就可以让那些不支持NSCoding协议的类去archive了; -
id (^customUnarchiveBlock)(NSData *data);
: 解码,同上; -
NSString *(^customFileNameBlock)(NSString *key)
: 当数据要存储为文件时,这个block会被调用并根据key来生成一个文件名,如果为空,则使用key的MD5来作为默认的文件名; -
errorLogsEnabled
: 是否允许打印日志;
limit attribute
-
countLimit
: 数据数量限制,默认为NSUIntegerMax;这并不是一个严格的限制(并不是一个一个的去删除,而是有一个基数,每次删除基数个数据);如果cache中的数量超过了这个数字,则会在后台线程中删除一部分数据; -
costLimit
: 磁盘的total开销超过此限制,则开始在后台线程中删除部分数据,这也不是一个严格的限制;默认为NSUIntegerMax -
ageLimit
: 时间限制,并不是严格的限制;超过此日期的数据会被在后台线程中删除;默认为DBL_MAX; -
freeDiskSpaceLimit
: 剩余空间限制,并不严格的限制;当剩余空间小于此限制时,部分数据会在后台被删除;默认为0; -
autoTrimInterval
: 自动去检查以上这些限制的时间间隔;默认为60s;
全局定义
宏定义
#define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
#define Unlock() dispatch_semaphore_signal(self->_lock)
使用两句宏定义来进行锁操作,来保证数据的安全性,其中self->_lock
是私有成员(一个自定义的并行队列);
在YYMemoryCache中,YY都是用的pthread_mutex_lock
来进行加锁以保证数据的安全性,不知道他为什么不用同一种,以后要对几种锁也研究下,写个博客啥的,这都是后话了。
全局变量
-
static const int extended_data_key
用来在runtime期间添加成员变量extended_data_key
,来获取extended data; -
static NSMapTable *_globalInstances;
全局的映射实例,是为了获取所有实例的weak引用的;
关于NSMapTable的具体内容,大家可以看官方文档或者是这边博客NSMapTable: 不只是一个能放weak指针的 NSDictionary -
static dispatch_semaphore_t _globalInstancesLock;
全局实例的锁;
还是稍微讲解下信号量dispatch_semaphore_t
吧
关于信号量,最重要的是一下三个函数;
1、 dispatch_semaphore_t dispatch_semaphore_create(long value)
* 初始化一个信号量,其中value表示初始化时信号量的值(有时候你可以理解为当前可用资源剩余个数);
2、 long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
* 此函数会先查看信号量dsema的值,如果是大于0的则在执行此函数语句的地方继续执行下面的语句并将dsema信号量值-1;
* 如果当前dsema的值小于等于0,则会阻塞当前线程,并等待到timeout;
* 在等待期间若该函数所处线程获取了信号量,即dsema的值大于0了,那么就会继 续执行该函数下面的语句;
* 在等待期间没有获得信号量,那么在timeout时间之后,就会继续执行该函数之后的语句了;
* 返回值为0表示在timeout时间内获取到了信号量,不为0则表示经过了timeout时间
3、 long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
* 将dsema的信号量做+1处理;
* 如果dsema之前的信号量小于0,则在返回之前会唤醒一个正在等待的线程去处理改信号量
* 返回值为0时表示当前并没有线程等待其处理的信号量;
* 返回值不为0表示当前有一个或多个线程等待其处理的信号量,改函数已经唤醒了一个线程去处理改信号量了;
静态函数
-
static int64_t _YYDiskSpaceFree()
使用NSFileManager获取系统剩余(NSFileSystemFreeSize)的size,返回此size(in bytes) -
static NSString *_YYNSStringMD5(NSString *string)
给指定字符串生成MD5串;- 使用
dataUsingEncoding
方法将string进行NSUTF8StringEncoding编码后转为NSData; - 初始化一个
CC_MD5_DIGEST_LENGTH
(16 digest length in bytes)长度的C数组result; - 使用
extern unsigned char *CC_MD5(const void *data, CC_LONG len, unsigned char *md)
函数,根据生成的data来填充result; - 将数组转为字符串并返回;
- 使用
-
static void _YYDiskCacheInitGlobal()
全局变量的初始化工作,以单例的形式对全局变量_globalInstancesLock
和_globalInstances
进行初始化;
_globalInstancesLock
初始化为dispatch_semaphore_create(1)
;
_globalInstances
初始化为一个对key强引用,对value弱引用,容量为0的NSMapTable; -
static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path)
获取 YYDiskCache 的实例;- 如果path为长度为0,直接返回nil;
- 使用
_YYDiskCacheInitGlobal()
去做初始化工作; - 使用
dispatch_semaphore_wait()
进行加锁操作; - 从
_globalInstances
中取出 path 对应的 value; - 使用
dispatch_semaphore_signal
进行解锁操作; - 返回取出的value;
-
static void _YYDiskCacheSetGlobal(YYDiskCache *cache)
- 将cache缓存到全局变量
_globalInstances
中 - 使用信号量进行加锁,解锁操作;
- 将cache缓存到全局变量
implementation实现
私有成员
-
YYKVStorage *_kv
: 实际进行操作的YYKVStorage实例; -
dispatch_semaphore_t _lock
: 用来加锁的信号量; -
dispatch_queue_t _queue
: 自定义的并行队列,所有对外的接口中异步执行的block都在此队列中执行;
私有方法
-
- (void)_trimRecursively
- 对缓存中的数据进行递归修剪,即使用给定的
_autoTrimInterval
来定期对数据进行cost, count, age, freeDiskSpace的检查; - 使用
dispatch_after
函数在优先级为Low的全局队列中执行方法_trimInBackground
; - 递归调用
_trimRecursively
;
- 对缓存中的数据进行递归修剪,即使用给定的
-
- (void)_trimInBackground
- 在
_queue
对了中异步执行了修剪操作; - 使用定义好的宏
Lock()
和Unlock()
进行加锁操作执行方法_trimToCost
,_trimToCount
,_trimToAge
,_trimToFreeDiskSpace
;
- 在
-
- (void)_trimToCost:(NSUInteger)costLimit
-
- (void)_trimToAge:(NSTimeInterval)ageLimit
-
- (void)_trimToCount:(NSUInteger)countLimit
这三个方法实际都是调用的YYKVStorage里面的方法执行的,大家可以看下YYKVStorage的博客去看具体的实现过程; -
- (void)_trimToFreeDiskSpace:(NSUInteger)targetFreeDiskSpace
实际是通过YYKVStorage获取总的items的开销,然后对比_YYDiskSpaceFree()
获取的总的内存空间,然后使用差值调用_trimToCost
方法来释放空间; -
- (NSString *)_filenameForKey:(NSString *)key
若已经定义好_customFileNameBlock
则使用block来获取key对应的filename来返回,否则使用默认的_YYNSStringMD5
方法获取到key对应的filename之后返回; -
- (void)_appWillBeTerminated
使用宏定义的锁加锁后,将私有变量_kv
置空;
public方法
Initializer
默认的初始化方法-init
和-new
都被UNAVAILABLE_ATTRIBUTE(不可用);
指定的初始化方法如下:
-
- (nullable instancetype)initWithPath:(NSString *)path
path必须为完整的路径名,如果已经存在此path的实例,则直接返回此实例,否则创建实例并返回;
实际调用的是- (nullable instancetype)initWithPath:(NSString *)path inlineThreshold:(NSUInteger)threshold
方法,指定threshold为1024 * 20; -
- (nullable instancetype)initWithPath:(NSString *)path inlineThreshold:(NSUInteger)threshold
指定的初始化方法(NS_DESIGNATED_INITIALIZER
),如果已经存在此path的实例,则直接返回此实例;-
threshold
:阈值,用来给属性inlineThreshold
赋值,一旦初始化后,不要再次改变此值; - 首先会从
_YYDiskCacheGetGlobal
函数中查找是否有此path对应的YYDiskCache,有就直接返回,没有就进行下面的操作; - 根据threshold的值会自动定义YYDiskCache的type;
- 使用默认值初始化各成员变量;
- 调用
_trimRecursively
开始定时检查cache; - 调用
_YYDiskCacheSetGlobal
将self存放在全局的_globalInstances中; - 添加
UIApplicationWillTerminateNotification
的通知,在APP被关闭时将_kv
置为nil;
-
Access Methods
-
- (BOOL)containsObjectForKey:(NSString *)key;
- 使用宏定义加锁后,使用YYKVStorage的
itemExistsForKey
方法来查找是否包含此key; - 此方法会阻塞当前线程直到读取完数据;
- 后面的其他会阻塞当前线程的方法与此方法类似,均是使用YYKVStorage中的API来执行具体的操作,使用
Lock()
和Unlock()
进行加锁和解锁;
- 使用宏定义加锁后,使用YYKVStorage的
-
- (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block;
- 在
_queue
中异步执行containsObjectForKey
方法,并在此之后执行block; - 后面其他非阻塞当前线程的方法与此方法类似,均是在
_queue
中执行对应的阻塞当前线程的方法然后执行block的;
- 在
-
- (nullable id<NSCoding>)objectForKey:(NSString *)key;
- 返回指定key对用的数据,此方法会阻塞当前线程,直到读取完数据;
如果存在
customUnarchiveBlock
,则会使用此block去做解档操作 -
- (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id<NSCoding> _Nullable object))block;
此方法会立即返回给block,并在后台线程中调用block -
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;
- 如果object为nil,则调用
removeObjectForKey
方法; - 如果不为空则阻塞当前线程直达写入完成为止;
如果存在
_customArchiveBlock
,则会使用此block去做归档操作 - 如果object为nil,则调用
-
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(void(^)(void))block;
此方法会立即返回,并在后台线程中执行block; -
- (void)removeObjectForKey:(NSString *)key;
阻塞当前线程,知道删完key对应的数据为止; -
- (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block;
立即返回,后台线程中执行block; -
- (void)removeAllObjects;
阻塞当前线程,知道删完所有数据为止; -
- (void)removeAllObjectsWithBlock:(void(^)(void))block;
立即返回,后台执行删除任务; -
- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress endBlock:(nullable void(^)(BOOL error))end;
立即返回,并在每次删除一些数据后调用progress,删除完后调用end; -
- (NSInteger)totalCount;
阻塞当前线程,计算完毕后返回cache中数据的总数; -
- (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block;
不阻塞,立即返回,block在后台执行 -
- (NSInteger)totalCost;
同- (NSInteger)totalCount;
-
- (void)totalCostWithBlock:(void(^)(NSInteger totalCost))block;
同- (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block;
Trim
-
- (void)trimToCount:(NSUInteger)count;
阻塞当前线程;根据LRU来remove objects; -
- (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block;
不阻塞当前线程,根据LRU remove objects -
- (void)trimToCost:(NSUInteger)cost;
同- (void)trimToCount:(NSUInteger)count;
-
- (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block;
同- (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block;
-
- (void)trimToAge:(NSTimeInterval)age;
同- (void)trimToCount:(NSUInteger)count;
-
- (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block;
同- (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block;
Extended Data
-
+ (nullable NSData *)getExtendedDataFromObject:(id)object;
类方法;runtime添加的类属性,设置指定object的extendedData; -
+ (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object;
类方法;获取指定object的extend数据;
网友评论