26
2017
09

YYCache源码分析(二) - YYDiskCache

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 instances
static 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];
    });
}
// 保存新的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对象
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();
}
上一篇:IntentService源码分析 下一篇:HTTP简介和总结