YYCache源码分析(二)
来源:互联网 发布:淘宝每月刷多少会封号 编辑:程序博客网 时间:2024/06/05 07:02
YYCache源码分析(二) - YYDiskCache
YYDiskCache
YYDiskCache采用的 SQLite 配合文件的存储方式,当单条数据小于 20K 时进行数据库缓存,数据越小 SQLite 读取性能越高;单条数据大于 20K 时进行文件缓存。
YYDiskCache.m方法实现
YYDiskCache的数据结构:
@implementation YYDiskCache { YYKVStorage *_kv; // 对数据进行缓存操作的对象 dispatch_semaphore_t _lock; // 同步锁,每次仅允许一个线程操作 dispatch_queue_t _queue; // 执行block的队列}
YYDiskCache中需要注意到的数据结构和函数:
/// weak reference for all instancesstatic NSMapTable *_globalInstances; // 全局字典,用来存放YYDiskCache对象static dispatch_semaphore_t _globalInstancesLock; //互斥锁,保证每次仅一个线程方法全局字典// 初始化字典和互斥锁static void _YYDiskCacheInitGlobal() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _globalInstancesLock = dispatch_semaphore_create(1); _globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0]; });}// 获取YYDiskCache对象static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) { if (path.length == 0) return nil; _YYDiskCacheInitGlobal(); dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER); id cache = [_globalInstances objectForKey:path]; dispatch_semaphore_signal(_globalInstancesLock); return cache;}// 保存YYDiskCache对象static void _YYDiskCacheSetGlobal(YYDiskCache *cache) { if (cache.path.length == 0) return; _YYDiskCacheInitGlobal(); dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER); [_globalInstances setObject:cache forKey:cache.path]; dispatch_semaphore_signal(_globalInstancesLock);}
每一条存储路径下,都对应一个YYDiskCache对象,不同的YYDiskCache都共享一个NSMapTable集合。在创建某条路径的YYDiskCache对象时,会首先查找集合,若该路径下的YYDiskCache对象存在,则从集合中获取。若没有,则重新创建。这样在不通路径下切换时,节省了大量时间。
NSMapTable对于NSDictionary对比:NSMapTable可以指定key/value是需要strong,weak,甚至是copy,如果使用的是weak,当key、value在被释放的时候,会自动从NSMapTable中移除这一项。NSMapTable中可以包含任意指针,使用指针去做检查操作。
NSDcitionary或者NSMutableDictionary中对于key和value的内存管理是:对key进行copy,对value进行强引用。NSDcitionary中对于key的类型,是需要key支持NSCopying协议,并且在NSDictionary中,object是由“key”来索引的,key的值不能改变,为了保证这个特性在NSDcitionary中对key的内存管理为copy,在复制的时候需要考虑对系统的负担,因此key应该是轻量级的,所以通常我们都用字符串和数字来做索引,但这只能说是key-to-object映射,不能说是object-to-object的映射。
NSMapTabTable更适合于我们一般所说的映射标准,它既可以处理key-to-value又可以处理object-to-object
YYDiskCache中实现定时清理缓存的方式与YYMemoryCache一样,首先在初始化中调用_trimRecursively方法。_trimRecursively方法的实现就是递归和dispatch_after结合的方式。
// 根据key值创建缓存文件名- (NSString *)_filenameForKey:(NSString *)key { NSString *filename = nil; // 自定义block的到文件名 if (_customFileNameBlock) filename = _customFileNameBlock(key); // md5加密得到文件名 if (!filename) filename = key.md5String; return filename;}
// 清理大小为targetFreeDiskSpace的磁盘空间- (void)_trimToFreeDiskSpace:(NSUInteger)targetFreeDiskSpace { if (targetFreeDiskSpace == 0) return; // 磁盘已缓存的大小 int64_t totalBytes = [_kv getItemsSize]; if (totalBytes <= 0) return; // 磁盘可用空间大小 int64_t diskFreeBytes = _YYDiskSpaceFree(); if (diskFreeBytes < 0) return; // 磁盘需要清理的大小 = 目标要清除的空间大小 - 可用空间大小 int64_t needTrimBytes = targetFreeDiskSpace - diskFreeBytes; if (needTrimBytes <= 0) return; // 磁盘缓存的空间大小限制 = 已缓存大小 - 需要清理的空间大小 int64_t costLimit = totalBytes - needTrimBytes; if (costLimit < 0) costLimit = 0; [self _trimToCost:(int)costLimit];}
// 磁盘空闲的大小static int64_t _YYDiskSpaceFree() { NSError *error = nil; // 获取主目录的文件属性,主目录下包含Document、Liberary等目录 NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:&error]; if (error) return -1; // 获取主目录的可用空间,即磁盘的可用空间 int64_t space = [[attrs objectForKey:NSFileSystemFreeSize] longLongValue]; if (space < 0) space = -1; return space;}
// 初始化字典(用来缓存YYDiskCache对象)与创建锁static void _YYDiskCacheInitGlobal() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 创建一把锁 _globalInstancesLock = dispatch_semaphore_create(1); // 初始化字典 _globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0]; });}
// 保存新的YYDiskCachestatic void _YYDiskCacheSetGlobal(YYDiskCache *cache) { if (cache.path.length == 0) return; _YYDiskCacheInitGlobal(); dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER); [_globalInstances setObject:cache forKey:cache.path]; dispatch_semaphore_signal(_globalInstancesLock);}
// 获取已经缓存的YYDiskCache对象static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) { if (path.length == 0) return nil; // 初始化字典(用来缓存YYDiskCache对象)与创建锁 _YYDiskCacheInitGlobal(); // 加锁 dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER); id cache = [_globalInstances objectForKey:path]; // 解锁 dispatch_semaphore_signal(_globalInstancesLock); return cache;}
初始化
- (instancetype)initWithPath:(NSString *)path inlineThreshold:(NSUInteger)threshold { self = [super init]; if (!self) return nil; // 1.根据path先从缓存里面找YYDiskCache(未找到再去重新创建实例) YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path); if (globalCache) return globalCache; // 2.重新创建实例 // 2.1创建cache,设置缓存类型 /** path:缓存路径 threshold:如果缓存数据>threshold,存储成文件,相反则存储到数据库,threshold默认20480kb threshold == 0:数据存储成单独的文件 threshold == NSUIntegerMax:数据全部存储到数据库 */ YYKVStorageType type; if (threshold == 0) { type = YYKVStorageTypeFile; } else if (threshold == NSUIntegerMax) { type = YYKVStorageTypeSQLite; } else { type = YYKVStorageTypeMixed; } // 2.2实例化YYKVStorage对象(YYKVStorage上面已分析,YYDiskCache的缓存实现都在YKVStorage) YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type]; if (!kv) return nil; // 2.3初始化数据 _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]; // 2.4放入NSMapTable中缓存 _YYDiskCacheSetGlobal(self); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil]; return self;}
添加缓存
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key { if (!key) return; if (!object) { // 缓存对象为null,删除缓存 [self removeObjectForKey:key]; return; } //获取扩展数据,用户可以在存储缓存数据前调用类方法设置扩展数据 NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object]; NSData *value = nil; /* 自定义block进行归档操作 _customArchiveBlock != nil时,将调用_customArchiveBlock将对象转成NSData类型数据 _customArchiveBlock的主要目的是:_customArchiveBlock代替不支持NSCoding协议的对象的归档 */ if (_customArchiveBlock) { value = _customArchiveBlock(object); } else { // 系统归档的方式 @try { value = [NSKeyedArchiver archivedDataWithRootObject:object]; } @catch (NSException *exception) { // nothing to do... } } if (!value) return; NSString *filename = nil; // 缓存方式不为sqlite类型且数据大小超过规定值,获取文件名 if (_kv.type != YYKVStorageTypeSQLite) { if (value.length > _inlineThreshold) { filename = [self _filenameForKey:key]; } } Lock(); [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData]; Unlock();}
- YYCache源码分析(二)
- YYCache源码分析(三)
- YYCache源码分析(一)
- 源码解析--YYCache
- iOS源码解析—YYCache(概述)
- iOS源码解析—YYCache(YYMemoryCache)
- iOS源码解析—YYCache(YYDiskCache)
- YYCache,TMCache,SDImageDiskCache的比较以及二级缓存分析
- Mangos源码分析(二)
- ConcurrentHashMap 源码分析 (二)
- Notepad++源码分析(二)
- gSOAP 源码分析(二)
- shopqi源码分析二
- VLC源码分析(二)
- mongodb源码分析(二)
- VLC源码分析(二)
- tomcat源码分析二
- Struts2源码分析二
- centos下jdk和tomcat的安装配置
- JavaScript知识总结(1)
- Hadoop学习笔记(1):概念和整体架构
- 千回百折:百度Java研发offer斩获记和经验分享
- Linux进程调度算法
- YYCache源码分析(二)
- Core Java 总结(异常类问题)
- YYCache源码分析(三)
- 详解Hadoop核心架构
- oos 文件下载
- 基于Dubbo的分布式系统架构(三):安装dubbo监控中心
- leetcode 17. Letter Combinations of a Phone Number
- ACM-ICPC(2017)北京赛区网络赛-E-Territorial Dispute(计算几何->凸包)
- 调整数组顺序使奇数位于偶数前面