YYCache之《二》 磁盘缓存

上一篇中,介绍了YYDiskCache的使用,本篇介绍一下它的实现。
YYDiskCache支持在一个app中创建多个缓存实例,每个独立的实例都必须以独立的路径为准,多次创建同一路径的cache,返回的可能是同一个实例。YYCache管理了一个全局集合,类型为NSMapTable,所有的DiskCache实例都保存在这个Map中,该集合是线程安全的,由一个单独的信号量机实例保证。 创建DiskCache对象会先从Map中找对应路径的,没有才会创建新的。


image
_globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];

NSMapTable是Foundation下的API,可以扩展key和value的内存语义,比NSDictionary要强大,上面这句话的意思就是,全局集合中的value是weak的,可以随时被释放,不会造成相互引用。

每一个YYDiskCache都由一个YYKVStorage,一个信号量锁,和一个并发任务队列构成。YYKYStorage封装了对SQL、文件的读写操作,信号量保证线程安全,并发队列提升效率。

@implementation YYDiskCache {
    YYKVStorage *_kv;
    dispatch_semaphore_t _lock;
    dispatch_queue_t _queue;
}

KVStorage的操作的两份内容 DB/File,结构如下:

/*
 File:
 /path/
      /manifest.sqlite
      /manifest.sqlite-shm
      /manifest.sqlite-wal
      /data/
           /e10adc3949ba59abbe56e057f20f883e
           /e10adc3949ba59abbe56e057f20f883e
      /trash/
            /unused_file_or_folder
 
 SQL:
 create table if not exists manifest (
    key                 text,
    filename            text,
    size                integer,
    inline_data         blob,
    modification_time   integer,
    last_access_time    integer,
    extended_data       blob,
    primary key(key)
 ); 
 create index if not exists last_access_time_idx on manifest(last_access_time);
 */

这个path的父目录即创建diskcache时外部设置的path。
/manifest.sqlite是整个cache的数据库,另外两个文件.sqlite-shm/.sqlite-wal 是数据库产生的临时文件,一个代表共享内存(shared memory),一个代表日志(write-ahead log)。DB删除的时候,需要把这两个文件一起删除掉。
/data/目录存储了我们缓存的所有文件,和实际文件之间的二进制对应关系可以自定义,默认是archeive方式,文件名也可以自定义,默认是MD5方式,上一篇文章中已经说过。
/trash/是data中文件删除前的一道屏障,相当于回收站。不过当前使用的时候,是在reset时机,这时,两个文件夹中的文件会被先后删除。

SQL 即在manifest.sql库中创建一个manifest表,以记录每一条缓存数据。包括以文件形式存储的,只是文件的话,inline_data为空。每次对缓存的操作都由last_access_time modification_time进行记录。

Storage中定义实现了各种增删改查的操作,以及数据库的建立关闭。尤其是数据库的操作,涉及每一个缓存对象,所以在对象dealloc的时候,向系统申请了后台额外时长,以防止后台时进程被系统杀死,导致存储异常。

- (void)dealloc {
    UIBackgroundTaskIdentifier taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{}];
    [self _dbClose];
    if (taskID != UIBackgroundTaskInvalid) {
        [_YYSharedApplication() endBackgroundTask:taskID];
    }
}

beginBackgroundTaskWithExpirationHandler 这个方法会申请3分钟左右的后台存活时间,ios7以前可以存活5-10分钟。这个方法必须与 endBackgroundTask 配对使用。

_YYSharedApplication() 的定义引出了引出了另一个概念:App Extension,看一下这个定义:

/// Returns nil in App Extension.
static UIApplication *_YYSharedApplication() {
    static BOOL isAppExtension = NO;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = NSClassFromString(@"UIApplication");
        if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES;
        if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES;
    });
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
    return isAppExtension ? nil : [UIApplication performSelector:@selector(sharedApplication)];
#pragma clang diagnostic pop
}

App Extension的概念相信大家也见过,就是apple定义的一些app增强功能,包括widget, 分享,快速回复等等。官方文档:https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/index.html#//apple_ref/doc/uid/TP40014214-CH20-SW1
它的基本生命周期图如下:

image

总结一下:
1、YYDiskCache线程安全
2、YYDiskCache维护了一个全局集合,用于存储各个Cache对象,value全部为弱引用
3、YYDiskCache的操作行为主要由YYKVStorage来代理,数据库创建打开关闭销毁,增删改查,文件读写。
4、YYKVStorage做了进程后台免杀死的基本保护,beginBackgroundTaskWithExpirationHandler和endBackgroundTask要成对出现。
5、UIApplication有可能是 App Extension,并没有sharedApplication方法,需要加以区分。
6、NSMapTable是Foundation的API,功能强大,支持对key和value内存语义更广泛的定义。

你可能感兴趣的:(YYCache之《二》 磁盘缓存)