YYKit-YYCache源码分析
YYCache是一个高性能iOS缓存框架,是YYKit组件之一。
框架架构
-
YYCache:该框架最外层接口,内部使用YYMemeoryCache和YYDiskCache进行内存缓存和磁盘缓存
-
YYMemoryCache:内存缓存,使用双向链表的LRU算法的缓存机制
-
YYDiskCache:磁盘缓存,使用文件存储和SQLite数据库存储,支持异步操作
-
YYKVOStorage:YYDiskCache的底层具体实现,用于磁盘缓存
YYCache API详解
-
YYCache的内部使用YYMemeoryCache和YYDiskCache进行内存缓存和磁盘缓存,API基本和NSCache保持一致,所有方法都是线程安全。
-
YYCache的属性和方法
@interface YYCache : NSObject /** 缓存名称 */ @property (copy, readonly) NSString *name; /** 内存缓存 对象 */ @property (strong, readonly) YYMemoryCache *memoryCache; /** 磁盘缓存 对象 */ @property (strong, readonly) YYDiskCache *diskCache; /** 初始化指定缓存名称的实例对象 具有相同名称的多个实例对象使缓存不稳定 */ - (nullable instancetype)initWithName:(NSString *)name; /** 初始化指定缓存路径的实例对象 */ - (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER; /** 便利初始化指定缓存名称的实例对象 */ + (nullable instancetype)cacheWithName:(NSString *)name; /** 便利初始化指定缓存路径的实例对象 */ + (nullable instancetype)cacheWithPath:(NSString *)path; - (instancetype)init UNAVAILABLE_ATTRIBUTE; + (instancetype)new UNAVAILABLE_ATTRIBUTE; /** 通过指定的key值判断是否存在缓存 */ - (BOOL)containsObjectForKey:(NSString *)key; /** 通过指定的key值判断是否存在缓存 带有Block回调 */ - (void)containsObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, BOOL contains))block; /** 返回指定的key值的缓存 */ - (nullable id<NSCoding>)objectForKey:(NSString *)key; /** 返回指定的key值的缓存 带有Block回调 */ - (void)objectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, id<NSCoding> object))block; /** 通过指定的key值和object 设置缓存 */ - (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key; /** 通过指定的key值和object 设置缓存 带有Block回调 */ - (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(nullable void(^)(void))block; /** 删除指定的key值的缓存 */ - (void)removeObjectForKey:(NSString *)key; /** 删除指定的key值的缓存 带有Block回调 */ - (void)removeObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key))block; /** 删除所有缓存 */ - (void)removeAllObjects; /** 删除所有缓存 带有Block回调 */ - (void)removeAllObjectsWithBlock:(void(^)(void))block; /** 删除所有缓存 带有删除进度和完成Block回调 该方法立即返回并在后台使用block执行clear操作。 */ - (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress endBlock:(nullable void(^)(BOOL error))end; @end
-
-
YYCache的方法实现
- (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; } - (BOOL)containsObjectForKey:(NSString *)key { // 先判断内存缓存是否存在 return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key]; } - (void)containsObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key, BOOL contains))block { if (!block) return; // 先判断内存缓存是否存在 if ([_memoryCache containsObjectForKey:key]) { // 回到主线程执行Block回调 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ block(key, YES); }); } else { // 不存在再判断磁盘缓存是否存在 [_diskCache containsObjectForKey:key withBlock:block]; } } - (id<NSCoding>)objectForKey:(NSString *)key { // 获取内存缓存 id<NSCoding> object = [_memoryCache objectForKey:key]; if (!object) { // 如果内存缓存不存在,再去获取磁盘缓存 object = [_diskCache objectForKey:key]; if (object) { // 如果磁盘缓存存在,把磁盘缓存存在存入内存缓存中 [_memoryCache setObject:object forKey:key]; } } return object; } - (void)setObject:(id<NSCoding>)object forKey:(NSString *)key { // 先写入内存缓存,再写入磁盘缓存 [_memoryCache setObject:object forKey:key]; [_diskCache setObject:object forKey:key]; } - (void)removeObjectForKey:(NSString *)key { // 先删除内存缓存,再删除磁盘缓存 [_memoryCache removeObjectForKey:key]; [_diskCache removeObjectForKey:key]; }
YYMemeryCache
- 缓存淘汰算法: 缓存支持 LRU (least-recently-used) 淘汰算法
- 缓存控制: 支持多种缓存控制方法:总数量、总大小、存活时间
YYMemeryCache的缓存淘汰算法
LRU(least-recently-used) 最近最少使用算法
- 核心思想:如果数据最近被访问过,那么将来被访问的几率也更高
- 策略:最近使用的数据放在最前面,这样长时间未使用数据在后面,淘汰删除
YYMemeryCache中LRU算法的具体实现:
在YYMemeryCache中LRU采用双向链表的方式实现,把添加或者使用的数据插入到链表最前面,删除数据是从链表最后面删除内存缓存。双向链表通过 _YYLinkedMapNode 和 _YYLinkedMapNode来实现
-
_YYLinkedMapNode: 链表节点 包含缓存数据的具体信息
@interface _YYLinkedMapNode : NSObject { @package /** 前一个缓存数据节点 */ __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic /** 后一个缓存数据节点 */ __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic /** 缓存数据的key */ id _key; /** 缓存数据值 */ id _value; /** 缓存数据花费成本 */ NSUInteger _cost; /** 缓存数据访问时间 */ NSTimeInterval _time; } @end
-
_YYLinkedMapNode:链表类,用保存和管理所有内存缓存数据节点
@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; /** 在链表中删除节点 */ - (void)removeNode:(_YYLinkedMapNode *)node; /** 如果尾部节点存在,就删除尾部节点 */ - (_YYLinkedMapNode *)removeTailNode; /** 在链表中删除所有 */ - (void)removeAll; @end
-
_YYLinkedMapNode:链表类具体实现
@implementation _YYLinkedMap - (instancetype)init { self = [super init]; // 创建空的字典 _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // 内存缓存默认释放在子线程 _releaseOnMainThread = NO; _releaseAsynchronously = YES; return self; } - (void)dealloc { CFRelease(_dic); } // 插入node节点,node设置为头节点 - (void)insertNodeAtHead:(_YYLinkedMapNode *)node { // 内存缓存节点添加到字典中 CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node)); // 内存缓存总开销增加node的开销 _totalCost += node->_cost; // 内存缓存总数量增加1 _totalCount++; if (_head) { // 头节点存在 // node节点的后一个节点是头节点 node->_next = _head; // 现在的头结点是前一个是node节点 _head->_prev = node; // node节点设置为头节点 _head = node; } else { //头节点不存在, 那么尾节点一定不存在,node节点同时设置为头结点和尾节点 _head = _tail = node; } } // 使node节点作为头节点 - (void)bringNodeToHead:(_YYLinkedMapNode *)node { // node 已经是头节点 if (_head == node) return; if (_tail == node) { // node是尾节点 // 使node的前一个节点为尾节点 _tail = node->_prev; // 使node的前一个节点,现在的尾节点的后一个节点为空 _tail->_next = nil; } else { // node不是尾节点 从链表中移除node节点 // node的前一个节点作为node后一个节点的前一个节点 node->_next->_prev = node->_prev; // node的后一个节点作为node的前一个节点的后一个节点 node->_prev->_next = node->_next; } // 头节点作为node节点的后一个节点 node->_next = _head; // node的前一个为空 node->_prev = nil; // nodeh节点作为头节点的前一个节点 _head->_prev = node; // node节点设置为头节点 _head = node; } // 从双向链表中移除node节点 - (void)removeNode:(_YYLinkedMapNode *)node { // 从字典中移除node节点 CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key)); // 内存缓存总开销删除node的开销 _totalCost -= node->_cost; // 内存缓存总数量减少1 _totalCount--; if (node->_next) node->_next->_prev = node->_prev; if (node->_prev) node->_prev->_next = node->_next; if (_head == node) _head = node->_next; if (_tail == node) _tail = node->_prev; } // 从双向链表中移除尾节点 - (_YYLinkedMapNode *)removeTailNode { if (!_tail) return nil; _YYLinkedMapNode *tail = _tail; // 从字典中移除尾节点 CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key)); // 内存缓存总开销删除尾节点的开销 _totalCost -= _tail->_cost; // 内存缓存总数量减少1 _totalCount--; if (_head == _tail) { // 头节点相等于尾节点时,头节点和尾节点都为空 _head = _tail = nil; } else { // 头节点相不等于尾节点时 // 尾节点的前一个节点作为尾节点 _tail = _tail->_prev; // 现在的尾节点的后一个为空 _tail->_next = nil; } return tail; } // 删除所有内存缓存 - (void)removeAll { _totalCost = 0; _totalCount = 0; _head = nil; _tail = nil; // 字典的内存缓存数量大于0 if (CFDictionaryGetCount(_dic) > 0) { CFMutableDictionaryRef holder = _dic; // 创建一个空的字典赋值给_dic _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // holder 在指定的线程中释放 if (_releaseAsynchronously) { dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); dispatch_async(queue, ^{ CFRelease(holder); // hold and release in specified queue }); } else if (_releaseOnMainThread && !pthread_main_np()) { dispatch_async(dispatch_get_main_queue(), ^{ CFRelease(holder); // hold and release in specified queue }); } else { CFRelease(holder); } } } @end
YYMemeryCache缓存清理策略:
-
init方法
- (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; }
-
_trimRecursively :递归清理内存缓存方法
- (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 { dispatch_async(_queue, ^{ // 删除内存缓存到指定开销 [self _trimToCost:self->_costLimit]; // 删除内存缓存到指定数量 [self _trimToCount:self->_countLimit]; // 删除内存缓存指定时间之前的缓存 [self _trimToAge:self->_ageLimit]; }); }
从上面的代码可以看出YYMemeryCache在init方法调用_trimRecursively,这个方法从它的具体实现,可以看出是递归调用清理内存缓存,从总大小、总数量、访问时间来控制缓存。
YYMemeryCache:监听UIApplicationDidReceiveMemoryWarningNotification和UIApplicationDidEnterBackgroundNotification两个通知,当收到通知时,删除所有内存缓存。
- (void)_appDidReceiveMemoryWarningNotification { if (self.didReceiveMemoryWarningBlock) { self.didReceiveMemoryWarningBlock(self); } if (self.shouldRemoveAllObjectsOnMemoryWarning) { [self removeAllObjects]; } } - (void)_appDidEnterBackgroundNotification { if (self.didEnterBackgroundBlock) { self.didEnterBackgroundBlock(self); } if (self.shouldRemoveAllObjectsWhenEnteringBackground) { [self removeAllObjects]; } }
-
YYMemeryCache线程安全
-
在YYMemeryCache中对内存缓存中添加、删除、查询的操作是都是使用pthread_mutex 互斥锁来使线程同步,来保证所有方法都是线程安全。
- (NSUInteger)totalCount { pthread_mutex_lock(&_lock); NSUInteger count = _lru->_totalCount; pthread_mutex_unlock(&_lock); return count; } - (NSUInteger)totalCost { pthread_mutex_lock(&_lock); NSUInteger totalCost = _lru->_totalCost; pthread_mutex_unlock(&_lock); return totalCost; } - (BOOL)releaseOnMainThread { pthread_mutex_lock(&_lock); BOOL releaseOnMainThread = _lru->_releaseOnMainThread; pthread_mutex_unlock(&_lock); return releaseOnMainThread; } - (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread { pthread_mutex_lock(&_lock); _lru->_releaseOnMainThread = releaseOnMainThread; pthread_mutex_unlock(&_lock); } - (BOOL)releaseAsynchronously { pthread_mutex_lock(&_lock); BOOL releaseAsynchronously = _lru->_releaseAsynchronously; pthread_mutex_unlock(&_lock); return releaseAsynchronously; } - (void)setReleaseAsynchronously:(BOOL)releaseAsynchronously { pthread_mutex_lock(&_lock); _lru->_releaseAsynchronously = releaseAsynchronously; pthread_mutex_unlock(&_lock); } - (BOOL)containsObjectForKey:(id)key { if (!key) return NO; pthread_mutex_lock(&_lock); BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key)); pthread_mutex_unlock(&_lock); return contains; } - (id)objectForKey:(id)key { if (!key) return nil; pthread_mutex_lock(&_lock); ...... pthread_mutex_unlock(&_lock); return node ? node->_value : nil; } - (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost { if (!key) return; if (!object) { [self removeObjectForKey:key]; return; } pthread_mutex_lock(&_lock); ...... pthread_mutex_unlock(&_lock); } - (void)removeObjectForKey:(id)key { if (!key) return; pthread_mutex_lock(&_lock); ...... pthread_mutex_unlock(&_lock); } - (void)removeAllObjects { pthread_mutex_lock(&_lock); [_lru removeAll]; pthread_mutex_unlock(&_lock); }
-
YYDiskCache
YYDiskCache是一个线程安全的磁盘缓存,它存储由SQLite支持的键值对和文件系统存储(类似于NSURLCache的磁盘缓存。
- 缓存淘汰算法: 缓存支持 LRU (least-recently-used) 淘汰算法
- 缓存控制: 支持多种缓存控制方法:总数量、总大小、存活时间
- 可以配置磁盘空间没有空闲时自动删除缓存数据
- 根据缓存数据对象大小来自动获取对象的存储类型
YYDiskCahce存储的具体实现是通过YYKVStroage来实现,在YYKVStorage有个枚举YYKVStorageType,这个是设置存储类型
typedef NS_ENUM(NSUInteger, YYKVStorageType) {
/// 缓存数据value仅仅存储在文件系统
YYKVStorageTypeFile = 0,
/// 缓存数据value仅仅存储在sqlite中
YYKVStorageTypeSQLite = 1,
/// 根据缓存数据value大小存储在文件系统并且存储在sqlite中
YYKVStorageTypeMixed = 2,
};
接下来,看下YYDiskCahce的初始化的方法,我们可以在里面看见存储类型的设置和其他方法的调用
- (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;
}
通过上面看到YYDiskCache是通过threshold(阈值)的大小来设置存储类型,threshold的默认值是1024 * 20,大小也就是20KB.为什么选择20KB?因为作者测试时,得到当单条数小于20k时,数据越小SQLite读取性能越高;单条数据大于20K时,直接写为文件速度会更快一些。
-
threshold == 0:,所有存储数据对象存储为文件
-
threshold == NSUIntegerMax: 所有存储数据对象存储到数据库
-
threshold 为其他值: 根据存储数据对象大小设置存储类型
在YYDiskCache的存储中,YYKVStorage使用YYKVStorageItem存储键值对和元数据
@interface YYKVStorageItem : NSObject
@property (nonatomic, strong) NSString *key; /// 键
@property (nonatomic, strong) NSData *value; /// 值
@property (nullable, nonatomic, strong) NSString *filename; /// 文件名称
@property (nonatomic) int size; /// 大小
@property (nonatomic) int modTime; /// 修改时间戳
@property (nonatomic) int accessTime; /// 最后访问时间
@property (nullable, nonatomic, strong) NSData *extendedData; /// 扩展数据 存储对象之前设置
@end
YYDiskCache读写
-
YYDiskCache写入
- YYDiskCache在写入时都会存储数据到SQLite数据库中,区别在于当文件名称不为空时, 元数据value不会存入到SQLite中,是存储到文件系统中.
// YYDiskCache 传入存储对象和key存储到磁盘 - (void)setObject:(id<NSCoding>)object forKey:(NSString *)key { // 判断key是否为空 if (!key) return; // 判断object是否为空 if (!object) { // 如果object为空 从磁盘缓存中删除以key的键的缓存数据x对象 [self removeObjectForKey:key]; return; } // 磁盘存储之前,获取扩展数据 NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object]; // 初始化磁盘存储的值 NSData *value = nil; // 判断是否存在自定义归档的Block if (_customArchiveBlock) { // 自定义归档的Block归档并赋值给value value = _customArchiveBlock(object); } else { @try { // 调用NSKeyedArchiver归档并赋值给value value = [NSKeyedArchiver archivedDataWithRootObject:object]; } @catch (NSException *exception) { // nothing to do... } } if (!value) return; // 初始化磁盘存储的文件名称 NSString *filename = nil; // 判断存储类型是否等于YYKVStorageTypeSQLite if (_kv.type != YYKVStorageTypeSQLite) { // 值的长度大于内部阈值 key采用md5加密并赋值给文件名称 if (value.length > _inlineThreshold) { filename = [self _filenameForKey:key]; } } // 加锁 采用信号量保证线程安全 #define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER) Lock(); // YYKVStorage保存到磁盘中 [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData]; // 解锁 #define Unlock() dispatch_semaphore_signal(self->_lock) Unlock(); } // YYKVStorage 存储 - (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData { if (key.length == 0 || value.length == 0) return NO; // 类型等于YYKVStorageTypeFile 并且filename为空 返回 不能存储到磁盘 if (_type == YYKVStorageTypeFile && filename.length == 0) { return NO; } // 文件名称不为空 执行文件缓存 if (filename.length) { // 执行文件缓存 返回BOOL值 文件存储失败就返回 if (![self _fileWriteWithName:filename data:value]) { return NO; } // 存储元数据到SQLite中 if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) { // 存储元数据到SQLite失败 文件缓存也要删除 [self _fileDeleteWithName:filename]; return NO; } return YES; } else { // 文件名称为空,不文件缓存 if (_type != YYKVStorageTypeSQLite) { // 存储类型不等于YYKVStorageTypeSQLite,判断是否缓存有key的文件名称,如果有就删除 NSString *filename = [self _dbGetFilenameWithKey:key]; if (filename) { [self _fileDeleteWithName:filename]; } } // 存储元数据到SQLite中 return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData]; } } // 执行SQL语句存储元数据到SQLite中 - (BOOL)_dbSaveWithKey:(NSString *)key value:(NSData *)value fileName:(NSString *)fileName extendedData:(NSData *)extendedData { NSString *sql = @"insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);"; ...... // 当文件名称为空时 存储value数据到SQLite中 if (fileName.length == 0) { sqlite3_bind_blob(stmt, 4, value.bytes, (int)value.length, 0); } else { // 当文件名称不为空时 不存储value数据到SQLite中,此时文件系统已缓存 sqlite3_bind_blob(stmt, 4, NULL, 0, 0); } int result = sqlite3_step(stmt); ...... return YES; }
-
YYDiskCache读取
- (id<NSCoding>)objectForKey:(NSString *)key { if (!key) return nil; Lock(); // 从YYKVStorage获取缓存YYKVStorageItem对象 YYKVStorageItem *item = [_kv getItemForKey:key]; Unlock(); if (!item.value) return nil; id object = nil; if (_customUnarchiveBlock) { // 自定义解档Block存在 执行Block解档 object = _customUnarchiveBlock(item.value); } else { @try { // 自定义解档Block不存在 执行NSKeyedUnarchiver解档 object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value]; } @catch (NSException *exception) { // nothing to do... } } // 解档对象存储并且存在扩展数据 设置扩展数据到YYDiskCache if (object && item.extendedData) { [YYDiskCache setExtendedData:item.extendedData toObject:object]; } return object; } - (YYKVStorageItem *)getItemForKey:(NSString *)key { if (key.length == 0) return nil; // 从SQLite中获取缓存的YYKVStorageItem对象 YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:NO]; if (item) { // 更新key值的访问时间 [self _dbUpdateAccessTimeWithKey:key]; // 如果文件名称不为空 去文件系统去获取缓存的value if (item.filename) { item.value = [self _fileReadWithName:item.filename]; // value为空 删除此s缓存对象 if (!item.value) { [self _dbDeleteItemWithKey:key]; item = nil; } } } return item; }
-
YYDiskCache的LRU算法
-
在YYDiskCache中存储的数据都是在SQLite中,当YYDiskCache的_trimRecursively和_trimInBackground检查到需要当磁盘总开销、缓存对象总数量、存活时间、磁盘空闲超过限定值,执行删除操作。通过last_access_time标识缓存数据的优先级,执行SQL语句返回last_access_time最后访问时间靠前的缓存对象执行删除操作。
- (BOOL)removeItemsToFitCount:(int)maxCount { ...... NSArray *items = nil; BOOL suc = NO; do { int perCount = 16; items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount]; for (YYKVStorageItem *item in items) { if (total > maxCount) { if (item.filename) { [self _fileDeleteWithName:item.filename]; } suc = [self _dbDeleteItemWithKey:item.key]; total--; } else { break; } if (!suc) break; } } while (total > maxCount && items.count > 0 && suc); if (suc) [self _dbCheckpoint]; return suc; } - (BOOL)removeItemsToFitSize:(int)maxSize { ...... NSArray *items = nil; BOOL suc = NO; do { int perCount = 16; items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount]; for (YYKVStorageItem *item in items) { if (total > maxSize) { if (item.filename) { [self _fileDeleteWithName:item.filename]; } suc = [self _dbDeleteItemWithKey:item.key]; total -= item.size; } else { break; } if (!suc) break; } } while (total > maxSize && items.count > 0 && suc); if (suc) [self _dbCheckpoint]; return suc; } // 按照最后访问时间查询指定数量的缓存数据 - (NSMutableArray *)_dbGetItemSizeInfoOrderByTimeAscWithLimit:(int)count { // 执行查询的SQL语句 NSString *sql = @"select key, filename, size from manifest order by last_access_time asc limit ?1;"; // 创建预处理SQL语句对象 sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; if (!stmt) return nil; // 绑定需要查询的数量到预处理SQL语句对象中 sqlite3_bind_int(stmt, 1, count); NSMutableArray *items = [NSMutableArray new]; // do whileh循环执行预处理SQL语句,遍历得到的结果 do { // 执行预处理SQL语句 int result = sqlite3_step(stmt); // SQLITE_ROW:查询到一行数据, 获取数据添加到items // SQLITE_DONE:结果集遍历完成,跳出循环 // result等于其他数据时, 跳出循环 if (result == SQLITE_ROW) { // 获取key char *key = (char *)sqlite3_column_text(stmt, 0); // 获取文件名称 char *filename = (char *)sqlite3_column_text(stmt, 1); // 获取缓存数据大小 int size = sqlite3_column_int(stmt, 2); NSString *keyStr = key ? [NSString stringWithUTF8String:key] : nil; if (keyStr) { YYKVStorageItem *item = [YYKVStorageItem new]; item.key = key ? [NSString stringWithUTF8String:key] : nil; item.filename = filename ? [NSString stringWithUTF8String:filename] : nil; item.size = size; [items addObject:item]; } } else if (result == SQLITE_DONE) { break; } else { if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); items = nil; break; } } while (1); return items; }
-
YYCache提高高性能缓存的框架的其他操作
-
CFMutableDictionaryRef: CFMutableDictionaryRef作为CoreFoundation框架的函数比NSMutableDictionary有更高的性能。
-
缓存的自动清理和释放都放到子线程处理:通过GCD把需要释放的对象捕捉到指定队列所在线程的Block中释放。下面的代码显示出来这样的操作 [holder count]; [node class]; 是捕捉对象到Block 避免编译器警告。
// 删除所有内存缓存 - (void)removeAll { ...... if (CFDictionaryGetCount(_dic) > 0) { CFMutableDictionaryRef holder = _dic; // holder 在指定的线程中释放 if (_releaseAsynchronously) { dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); dispatch_async(queue, ^{ CFRelease(holder); // 捕捉holder到指定队列所在线程的Block中释放 }); } else if (_releaseOnMainThread && !pthread_main_np()) { dispatch_async(dispatch_get_main_queue(), ^{ CFRelease(holder); // 捕捉holder到指定队列所在线程的Block中释放 }); } else { CFRelease(holder); } } } - (void)_trimToCount:(NSUInteger)countLimit { ...... NSMutableArray *holder = [NSMutableArray new]; ...... if (holder.count) { dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); dispatch_async(queue, ^{ [holder count]; // 捕捉holder到指定队列所在线程的Block中释放 }); } } - (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost { ...... _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key)); 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]; // 捕捉node到指定队列所在线程的Block中释放 }); } else if (_lru->_releaseOnMainThread && !pthread_main_np()) { dispatch_async(dispatch_get_main_queue(), ^{ [node class]; // 捕捉node到指定队列所在线程的Block中释放 }); } } ...... }
-
使用性能更好的 SQLite : SQLite官网最新的SQlite版本比iOS 系统自带的 sqlite3.dylib 性能要高很多。
#if __has_include(<sqlite3.h>) #import <sqlite3.h> #else #import "sqlite3.h" #endif
-
使用性能更好的锁 YYCache采用dispatch_semaphore 和 pthread_mutex 两种锁用于线程同步,这两种的性能是仅次于OSSpinLock 和 os_unfair_lock。作者最开始在内存缓存中使用的OSSpinLock,OSSpinLock由于线程不安全问题被弃用。作为性能最好的锁os_unfair_lock,这个是iOS10发布的,可能作者没有更新此框架或者考虑iOS10以下的原因,所以没有用os_unfair_lock。
网友评论