YYCache源码分析

article/2025/10/4 4:11:18

YYCache是OC用于缓存的第三方框架。

  • YYCache:同时实现内存缓存和磁盘缓存,且是线程安全的。
  • YYDiskCache:实现磁盘缓存,所有的API是线程安卓的,内部也采用了LRU淘汰算法,主要是SQLite和文件存储两种方式。
  • YYKVStorage:实现磁盘缓存,不推荐直接使用此类,该类不是线程安全的。
  • YYMemoryCache:实现内存缓存,所有的API是线程安全的,也是采用了LRU淘汰算法来提高性能。

LRU淘汰算法:

LRU(Least recently used,最近最少使用)算法,根据访问的历史记录来对数据进行淘汰。

  • 有新数据加入时添加到链表的头部。
  • 每当缓存命中(缓存数据被访问),则将数据移动到链表头部。
  • 每当链表满的时候,将链表尾部的数据丢弃。

在YYMemoryCache中使用双向链表和NSDictionary实现了LRU淘汰算法。

线程安全-锁:

YYCache使用到了两种锁

  • OSSpinLock:自旋锁
  • dispatch_semaphore:信号量,当信号量为1时充当锁进行使用

内存缓存使用的是pthread_mutex,由于pthread_mutex相当于do while忙等,等待时会消耗大量的CPU资源。

@interface _YYLinkedMapNode : NSObject {@package__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic,指向上一个节点__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic,指向下一个节点id _key;// 缓存的keyid _value;// 缓存的对象NSUInteger _cost;// 内存消耗NSTimeInterval _time;// 缓存时间
}
@end

_YYLinkedMapNode:链表的节点

  • _prev、_next:分别表示指向上一个节点、下一个节点
  • _key:缓存的key
  • _value:缓存对象
  • _cost:内存消耗
  • _time:缓存时间
@interface _YYLinkedMap : NSObject {@packageCFMutableDictionaryRef _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;// 是否在主线程释放_YYLinkedMapNodeBOOL _releaseAsynchronously;// 是否异步释放_YYLinkedMapNode
}

_YYLinkedMap:链表

  • _dic:用来保存节点
  • _totalCost:总缓存开销
  • _head、_tail:头节点、尾节点
  • _releaseOnMainThread:是否在主线程释放_YYLinkedMapNode
  • _releaseAsynchronously:是否异步释放_YYLinkedMapNode

  • 插入节点到头部(- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;)
  • 将某个节点移动到头部(- (void)bringNodeToHead:(_YYLinkedMapNode *)node;)
  • 移除特定节点(- (void)removeNode:(_YYLinkedMapNode *)node;)
  • 移除尾部节点(- (_YYLinkedMapNode *)removeTailNode;)
  • 移除所有节点(- (void)removeAll;)

移除所有节点源码:

- (void)removeAll {_totalCost = 0;_totalCount = 0;_head = nil;_tail = nil;if (CFDictionaryGetCount(_dic) > 0) {CFMutableDictionaryRef holder = _dic;_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);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);}}
}

通过双向链表来对数据进行操作,以及使用NSDictionary实现了LRU淘汰算法。时间复杂度O(1),5种操作基本是都是对节点的基本操作。

YYMemoryCache

这边介绍两个主要操作:添加缓存,查找缓存

添加缓存

-(void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost
{if(!key)return;if(!object){[self removeObjectForKey:key];// 缓存对象为nil时,直接移除return;}pthread_mutex_lock(&_lock);// 为了保证数据安全,数据操作前加锁_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic,(__briddge 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->_totalCount > _countLimit){// 判断当前的缓存进行是否超出了设定值,若超出则进行整理dispatch_async(_queue,^{[self trimToCost:_costLimit];});}if(_lru->_totalCost > _costLimit)// 每次添加数据仅有一个,数量上超出时,直接移除尾部那个object{_YYLinkedMapNode *node = [_lru removeTailNode];//线程操作,异步释放if(_lru->_releaseAsynchronousely){dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue : YYMemoryCacgeGetReleaseQueue();dispatch_async(queue,^{[node class];//通过node来对其进行持有,以至于不会在方法调用结束的时候被销毁});}else if(_lru->_releaseOnMainThread && !pthread_main_np){dispatch_async(dispatch_get_main_queue(),^{[node class];//通过node来对其进行持有,以至于不会在方法调用结束的时候被销毁});}}pthread_mutex_unlock(&_lock);// 操作结束,解锁
}

获取缓存

// 从memory 中取数据时,根据LRU原则,将最新取出的obejct放在栈头
-(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;
}    

YYKVStore

该文件主要以两种方式来实现磁盘缓存:SQLite、File,使用两种方式混合进行数据存储主要是为了提高读写效率。写入数据时,SQLite要比文件的方式更快;读取数据的速度取决于文件的大小。据测试,在iphone6中,当文件的大小超过20kb时,File要比SQLite快的多。所以大文件存储时建议采用File的方式,小文件更适合用SQLite.

Save:

- (BOOL)saveItemWithKey:(NSString *)keyvalue:(NSData *)valuefilename:(nullable NSString *)filenameextendedData:(nullable NSData *)extendedData
{//条件不符合if(key.length == 0 || value.length == 0)return NO;if(_type == YYKVStorageTypeFile && filename.length == 0){return NO;    }if(filename.length)// filename 存在,sqlite 和filename两种方式进行{// 用文件进行存储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{// filename不存在时,使用sqliteif(_type != YYKVStorageTypeSDLite){// 根据key删除对应的file文件NSString *filename = [self _dbGetFilenameWithKey:key];if(filename){[self _fileDeleteWithName:filename];}}// sqlite 进行存储return [self _dbSaveWithKey:key value:value filename:nil extendedData:extendedData];}
}

Remove

-(BOOL)removeItemForkey:(NSString *)key
{if(key.length == 0) return NO;switch(_type){case YYKVStorageSQLite:{return [self _dbDeleteItemWithKey:key];// 删除SQLite文件}break;case YYKVStorageTypeFile:case YYKVStorageTypeMixed:{NSString *filename = [self _dbGetFilenameWithKey:key];// 获取filenameif(filename){[self _dbDeleteWithName:filename];// 删除filename对应的file}return [self _dbDeleteItemWithKey:key];// 删除sqlite}break;default:return NO;}
}

Get

- (NSData *)getItemValueForKey:(NSString *)key
{if(key.length == 0) return nil;NSData *value = nil;switch(_type){case YYKVStorageTypeFile:{NSString *filename = [self _dbGetFilenameWithKey:key];if(filename)// 根据filename获取File{value = [self _fileReadWithName:filename];if(!value)// 当value 不存在,用对应的key删除SQLite数据{[self _dbDeleteItemWithKey:key];return nil;}}}break;case YYKVStorageTypeSQLite:// SQLite方式获取{value = [self _dbGetValueWithKey:key];}break;case YYKVStorageTypeMixed:{NSString *filename = [self _dbGetFilenameWithKey:key];if(filename)//filename 存在文件获取,不存在SQLite方式获取{value = [self _fileReadWithName:filename];if(!value){[self _dbDeleteItemWithKey:key];value = nil;}}else{value = [self _dbGetValueWithKey:key];}}break;}if(value){[self _dbUpdateAccessTimeWithKey:key];//更新文件操作时间}return value;
}

File方式主要是用的writeToFile进行存储,SQLite之际是用sqlite3来对文件进行操作。

YYDiskCache

YYDiskCache是对YYKVStorage进行的一次封装,是线程安全的,这边的操作使用的是dispatch_semaphore_signal来确保线程的安全。另外结合LRU淘汰算法,根据文件的大小自动选择存储方式来表达到更好的性能。

- (instancetype)initWithPath:(NSString *)pathinlineThreshold:(NSUInteger)threshold
{self = [super init];if(!self) return nil;//获取缓存的 YYDiskCacheYYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);if(globalCache) return globalCache;//确定存储的方式YYKVStorageType type;if(threshold == 0){type = YYKVStorageTypeFile;}else if(threshold == NSUIntegerMax){type = YYKVStorageTypeSQLite;    }else{type = YYKVStorageTypeMixed;}//初始化 YYKVStorageYYKVStorage *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);//通知 APP即将被杀死时[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];return self;
}

 


http://chatgpt.dhexx.cn/article/8PyFjAJM.shtml

相关文章

Guava Cache、LoadingCache本地缓存的使用

前言:由于用户系统重构存在新老版本,网关对部分数据进行路径写死,所以为了避免频繁请求接口对网关日志记录进行本地缓存处理,学习过程中有Cache和LoadingCache两种模式,一番测试下都可以正常使用,特此记录 准备工作 添加依赖 &l…

YYCache,TMCache,SDImageDiskCache的比较以及二级缓存分析

YYCache传送门 点击打开链接 内存缓存 通常一个缓存是由内存缓存和磁盘缓存组成,内存缓存提供容量小但高速的存取功能,磁盘缓存提供大容量但低速的持久化存储。相对于磁盘缓存来说,内存缓存的设计要更简单些,下面是我调查的一些…

EVCache

EVCache介绍 EVCache是一个开源、快速的分布式缓存,是基于Memcached的内存存储和Spymemcached客户端实现的,是Netflix(网飞)公司开发的 E:Ephemeral:数据存储是短暂的,有自身的存活时间V&#…

cache 缓存

缓存原理 测试样例 验证码 获取验证码 验证 验证码是否正确 idea 启动缓存 手机验证码 idea 手机 获得验证码 ehchace 数据淘汰策略 使用redis 然后启动 redis 服务器 redis-server.exe redis.windows.conf 启动redis 客户端redis-cli.exe time-to-live 最大活动时间 缓…

YYCache源码解读 (一)

YYCache YYCache是 ibireme 大神在2015年设计的一个IOS缓存工具。   通常来讲,一个缓存包括内存缓存和磁盘缓存。YYCache在磁盘缓存的设计上使用数据库sqlite映射和文件系统映射的方式进行存储,内存中设计了一个双向链表的结构存储数据结点。内存和磁盘…

Guava Cache本地缓存

目录 本地缓存 回顾 Guava Cache介绍 Guava Cache使用 创建 删除 Guava Cache底层实现 本地缓存与分布式缓存对比 缓存三大问题 本地缓存 实现:CurrentHashMap、Guava Cache 缓存在应用服务器,全局变量,JVM缓存 回顾 JVM内存 方…

缓存(ehcache/guavaCache使用)

单机缓存 ehcache:单独使用:与spring集成:编程式操作:注解式使用: 与springboot集成: guava cache:单独使用:spring/springboot集成:自定义KeyGenerator: 自定…

iOS 缓存框架YYCache学习

文章目录 前言一、YYCache的来源二、YYCache的结构1. YYMemoryCache1.1 最近最少使用—LRU(Least Frequently Used)1.2 基于LRU的增删改查1.2.1 增加数据1.2.2 删除数据1.2.3 查找修改数据1.2.4 YYMemoryCache的增删改查 2.YYDiskCache 总结参考文章 前言 提示:这篇…

深入理解YYCache缓存策略

文章目录 前言几个主要成员类1 YYCache2 YYMemoryCache3 YYDiskCache 实例化1 实例方法2 构造器方法 查1 检查是否有缓存2 读缓存 增1 写内存缓存2 写磁盘缓存 删1 清空内存缓存2 清空磁盘缓存 YYMemoryCache 初始化做了什么总结本文完 前言 YYCache是著名iOS框架YYKit的一个组…

Unity OnDestroy 调用

Test Code private GameObject _temp;_temp Instantiate(Resources.Load("gameObject original"), parent) as GameObject;if (Input.GetMouseButtonDown(1)) {Destroy(_temp);_temp null; }试验结果(场景中本来存在的gameObjec或Resources出来的game…

报错原因高的地图调用mapView.onDestroy() 崩溃问题

以前在android app中使用地图的项目需要使用高德地图。 按照高德地图的开发文档创建测试项目,导入依赖,很快就成功显示了地图,然后在退出地图Activity时,app立即崩溃,通过追踪,发现是在销毁地图时出现崩溃…

Android跨进程通信Client Crash后Server端onDestroy

hi,粉丝朋友大家好! 好久没有给大家写blog了,哈哈,这里说声抱歉!实在家里比较忙,今天就来给大家分享一个跨进程专题课中学员问的一个问题,blog就来解答一下这个问题。 问题背景: 视频…

Activity onDestroy延迟回调

前端时间工作的时候遇到了两个奇怪的问题: 使用百度步行导航的时候,开启导航后立即退出,再次进入的时候就会黑屏;使用度小满支付的时候,当支付成功后页面一直显示loading,过了10s左右才恢复正常。 这看似…

基于MFC的OpenDDS发布订阅例子(PubSubDemo)

在编译完成Message.idl,产生MessageCommon.dll和相应的MessageTypeSupport的.h头文件和.cpp文件(MessageTypeSupportImpl.h、MessageTypeSupportC.h)的基础上,新建PubSubDemo.sln和工程PubSubDemo.vcxproj,并开始编码,实现基于Message的发布和订阅流程。 1)新建基于Dia…

OPenDDS程序 的 实现+运行

标题DDS程序实现和运行 本文记录了Windows10环境下OpenDDS环境搭建,idl自定义,代码生成,代码编写的全过程。 一、环境搭建 1.详细情况请参考开发笔记:1. OpenDDS环境搭建-Windows 10.note 编译好后生成了两个文件夹“OpenDDS-3.…

OpenDDS-1

转自:软件开发.OpenDDS 设计智能座舱时ECU之间通信及与TSP通信选择使用OpenDDS是可以的,因此不少人都认为OpenDDS是属于汽车以太网(Aumotive Ethernet,AE),但事实上autosar AE中定义中有SomeIP、DoIP、AVB…

Java程序调用OpenDDS

一、前言 前面我们用三篇博客介绍了 OpenDDS在WIndows上的环境配置 Windows下的OpenDDS编译(超详细)_山中野竹的博客-CSDN博客_opendds windows 三种方式运行发布订阅示例程序 OpenDDS运行示例(Messenger)程序_山中野竹的博客-C…

OpenDDS运行实例

因为OpenDDS是分布式的部署,所以一般发布端和订阅端都不在同一台电脑上。 我在同一台电脑上进行测试,所以ip地址为:127.0.0.1 1.发布端 1.1 新建ior文件 在根目录先新建repo.ior文件: IOR:010000001e00000049444c3a4f70656e44…

Java调用OpenDDS(1)-编译安装openDDS-补上了所有网络上其他文章遗漏的细节

Java调用OpenDDS过程中踩了很多坑,记录一下。 提纲 1、DDS简介 2、DDS协议的实现产品 3、OpenDDS安装过程 1、DDS简介 DDS指的是Data Distribution Service,也即数据分发服务,是OMG(Object Management Group,对象管理…

OpenDDS自学

前言 最近做毕设要做一个DDS系统和TISA系统的网关,完全没有基础,只好对着OpenDDS的Developers’ Guide和《分布式系统实时发布/订阅数据分发技术》这本书一点一点学(顺便吐槽这本书就是guide的翻译版,很多语句不通)。遇到很多问题&#xff0…