1. 初始化
[[YYCache alloc] initWithName:@""];
- (instancetype) init {
NSLog(@"Use \"initWithName\" or \"initWithPath\" to create YYCache instance.");
return [self initWithPath:@""];
}
- (instancetype)initWithName:(NSString *)name {
if (name.length == 0) return nil;
NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
NSString *path = [cacheFolder stringByAppendingPathComponent:name];
return [self initWithPath:path];
}
- (instancetype)initWithPath:(NSString *)path {
if (path.length == 0) return nil;
YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];
if (!diskCache) return nil;
NSString *name = [path lastPathComponent];
YYMemoryCache *memoryCache = [YYMemoryCache new];
memoryCache.name = name;
self = [super init];
_name = name;
_diskCache = diskCache;
_memoryCache = memoryCache;
return self;
}
+ (instancetype)cacheWithName:(NSString *)name {
return [[self alloc] initWithName:name];
}
+ (instancetype)cacheWithPath:(NSString *)path {
return [[self alloc] initWithPath:path];
}
- YYCache提供静态方法和实例方法初始化,静态方法本质还是调用实例方法初始化。
- 实例方法提供name和path初始化,name会构建路径使用path方法进行初始化
- 初始化时有YYMemoryCache和YYDiskCache。
- 使用name进行初始化时,每个name对应都会生成一个文件夹,数据库。
2. YYMemoryCache内存缓存
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
if (!key) return;
if (!object) {
[self removeObjectForKey:key];
return;
}
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
NSTimeInterval now = CACurrentMediaTime();
if (node) {
_lru->_totalCost -= node->_cost;
_lru->_totalCost += cost;
node->_cost = cost;
node->_time = now;
node->_value = object;
[_lru bringNodeToHead:node];
} else {
node = [_YYLinkedMapNode new];
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
[_lru insertNodeAtHead:node];
}
if (_lru->_totalCost > _costLimit) {
dispatch_async(_queue, ^{
[self trimToCost:_costLimit];
});
}
if (_lru->_totalCount > _countLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
pthread_mutex_unlock(&_lock);
}
从setObject:forKey:withCost看出YYMemoryCache的重要数据结构_YYLinkedMap
2.1 _YYLinkedMap
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly
NSUInteger _totalCost;
NSUInteger _totalCount;
_YYLinkedMapNode *_head; // MRU, do not change it directly
_YYLinkedMapNode *_tail; // LRU, do not change it directly
BOOL _releaseOnMainThread;
BOOL _releaseAsynchronously;
}
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node{
...
}
- (void)bringNodeToHead:(_YYLinkedMapNode *)node{
...
}
_YYLinkedMap是一个双向链接。
insertNodeAtHead如果是新数据时,会把node插入在链表头部
bringNodeToHead在设置缓存时,如果链表已经包含node节点,会移动节点到头部。
CFMutableDictionaryRef _dic,存储了key,model,方便快速查找。
2.2 setObject:forKey
- (void)setObject:(id)object forKey:(id)key {
[self setObject:object forKey:key withCost:0];
}
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
if (!key) return;
if (!object) {
[self removeObjectForKey:key];
return;
}
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
NSTimeInterval now = CACurrentMediaTime();
if (node) {
_lru->_totalCost -= node->_cost;
_lru->_totalCost += cost;
node->_cost = cost;
node->_time = now;
node->_value = object;
[_lru bringNodeToHead:node];
} else {
node = [_YYLinkedMapNode new];
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
[_lru insertNodeAtHead:node];
}
if (_lru->_totalCost > _costLimit) {
dispatch_async(_queue, ^{
[self trimToCost:_costLimit];
});
}
if (_lru->_totalCount > _countLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
pthread_mutex_unlock(&_lock);
}
- key不能为空,为空直接return
- value不能为空,为空则需要移除对应的key
- 获取缓存中的node
- 如果node为空,生成一个node节点,insertNodeAtHead插入到链表头部
- 如果node不为空,bringNodeToHead 修改时间并把node节点移动到链表头部
- 采用LRU算法,使用过即在最前面
- trimToCost:移除超出Cost的节点,removeTailNode移除掉超出count节点
2.3 _trimToCost
- (void)_trimToCost:(NSUInteger)costLimit {
BOOL finish = NO;
pthread_mutex_lock(&_lock);
if (costLimit == 0) {
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCost <= costLimit) {
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru->_totalCost > costLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
usleep(10 * 1000); //10 ms
}
}
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // release in queue
});
}
}
- (instancetype)init {
self = super.init;
...
_countLimit = NSUIntegerMax;
_costLimit = NSUIntegerMax;
_ageLimit = DBL_MAX;
...
}
- costLimit = 0时,标识_costLimit已经初始化;_lru->_totalCost <= costLimit 在正常范围内
- finish为NO,移除超出cost的节点;pthread_mutex_trylock判断是否有其他线程锁了,则10ms之后再试;否则移除removeTailNode
- (_YYLinkedMapNode *)removeTailNode {
if (!_tail) return nil;
_YYLinkedMapNode *tail = _tail;
CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
_totalCost -= _tail->_cost;
_totalCount--;
if (_head == _tail) {
_head = _tail = nil;
} else {
_tail = _tail->_prev;
_tail->_next = nil;
}
return tail;
}
- 移除_dic中的key,value
- 尾部指针指向尾部倒数第二个指针,再把最后一个指针置nil
2.4 countLimit
if (_lru->_totalCount > _countLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
这一段只移除tailNode,而没有循环判断,countLimit是精确的,每次增加或修改了countLimit.
2.5 objectForKey:
- (id)objectForKey:(id)key {
if (!key) return nil;
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
node->_time = CACurrentMediaTime();
[_lru bringNodeToHead:node];
}
pthread_mutex_unlock(&_lock);
return node ? node->_value : nil;
}
- 使用时从字典获取node
- 如果node不为空,更新时间,把节点移动到头部。
符合LRU算法
2.6 内存管理
- (instancetype)init {
self = super.init;
pthread_mutex_init(&_lock, NULL);
_lru = [_YYLinkedMap new];
_queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);
_countLimit = NSUIntegerMax;
_costLimit = NSUIntegerMax;
_ageLimit = DBL_MAX;
_autoTrimInterval = 5.0;
_shouldRemoveAllObjectsOnMemoryWarning = YES;
_shouldRemoveAllObjectsWhenEnteringBackground = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
[self _trimRecursively];
return self;
}
2.6.1 清空链表数据
收到内存警告或进入后台时,清空缓存数据
2.6.2 _trimRecursively
每隔_autoTrimInterval时默认5s,检查Cost,Count,Age并清空过期数据
3. YYDiskCache持久化缓存
- (instancetype)initWithPath:(NSString *)path {
return [self initWithPath:path inlineThreshold:1024 * 20]; // 20KB
}
- (instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold {
self = [super init];
if (!self) return nil;
YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);
if (globalCache) return globalCache;
YYKVStorageType type;
if (threshold == 0) {
type = YYKVStorageTypeFile;
} else if (threshold == NSUIntegerMax) {
type = YYKVStorageTypeSQLite;
} else {
type = YYKVStorageTypeMixed;
}
YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type];
if (!kv) return nil;
_kv = kv;
_path = path;
_lock = dispatch_semaphore_create(1);
_queue = dispatch_queue_create("com.ibireme.cache.disk", DISPATCH_QUEUE_CONCURRENT);
_inlineThreshold = threshold;
_countLimit = NSUIntegerMax;
_costLimit = NSUIntegerMax;
_ageLimit = DBL_MAX;
_freeDiskSpaceLimit = 0;
_autoTrimInterval = 60;
[self _trimRecursively];
_YYDiskCacheSetGlobal(self);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
return self;
}
- initWithPath默认大小20kb
- 存储的核心类是YYKVStorage,存储分文件、数据库及混合存储。
- YYKVStorage封装了数据库和文件的操作。
3.1 _trimRecursively
磁盘60s会检查一次,也可以手动设置
- (void)_trimRecursively {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self _trimInBackground];
[self _trimRecursively];
});
}
- (void)_trimInBackground {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
if (!self) return;
Lock();
[self _trimToCost:self.costLimit];
[self _trimToCount:self.countLimit];
[self _trimToAge:self.ageLimit];
[self _trimToFreeDiskSpace:self.freeDiskSpaceLimit];
Unlock();
});
}
3.2 setObject:forKey
- (void)setObject:(id)object forKey:(NSString *)key {
if (!key) return;
if (!object) {
[self removeObjectForKey:key];
return;
}
NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];
NSData *value = nil;
if (_customArchiveBlock) {
value = _customArchiveBlock(object);
} else {
@try {
value = [NSKeyedArchiver archivedDataWithRootObject:object];//转成NSData
}
@catch (NSException *exception) {
// nothing to do...
}
}
if (!value) return;
NSString *filename = nil;
if (_kv.type != YYKVStorageTypeSQLite) {
if (value.length > _inlineThreshold) {
filename = [self _filenameForKey:key];
}
}
Lock();
[_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
Unlock();
}
- _customArchiveBlock自定义block,如果没有则使用系统方法object转data
- saveItemWithKey保存key,value
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {
if (key.length == 0 || value.length == 0) return NO;
if (_type == YYKVStorageTypeFile && filename.length == 0) {
return NO;
}
if (filename.length) {
if (![self _fileWriteWithName:filename data:value]) {
return NO;
}
if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {
[self _fileDeleteWithName:filename];
return NO;
}
return YES;
} else {
if (_type != YYKVStorageTypeSQLite) {
NSString *filename = [self _dbGetFilenameWithKey:key];
if (filename) {
[self _fileDeleteWithName:filename];
}
}
return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
}
}
- filename不为空时,_fileWriteWithName写入文件
- _dbSaveWithKey保存到数据库
3.3 objectForKey:
- (id)objectForKey:(NSString *)key {
if (!key) return nil;
Lock();
YYKVStorageItem *item = [_kv getItemForKey:key];
Unlock();
if (!item.value) return nil;
id object = nil;
if (_customUnarchiveBlock) {
object = _customUnarchiveBlock(item.value);
} else {
@try {
object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value];//data转成对象
}
@catch (NSException *exception) {
// nothing to do...
}
}
if (object && item.extendedData) {
[YYDiskCache setExtendedData:item.extendedData toObject:object];
}
return object;
}
- (YYKVStorageItem *)getItemForKey:(NSString *)key {
if (key.length == 0) return nil;
YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:NO];
if (item) {
[self _dbUpdateAccessTimeWithKey:key];//使用后需要更新时间
if (item.filename) {
item.value = [self _fileReadWithName:item.filename];
if (!item.value) {
[self _dbDeleteItemWithKey:key];
item = nil;
}
}
}
return item;
}
总结:
- YYCache很好的支持了对象的持久化问题,采用文件与数据库结合的方式进行存储。
- 同时对象存储采用序列化的方式,解决了不同对象差异的存储问题。
- 内存采用双链表实现LRU算法,磁盘存储采用最后更新时间的方式来实现LRU算法。
这里我有个小疑问:磁盘采用60s的方式进行轮询清理,每个YYCache对象都有60s,当有多个YYCache对象使用时,是否需要考虑下60s的长度问题。