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();}
原创粉丝点击