美文网首页
YYCache源码阅读一YYYDiskCache

YYCache源码阅读一YYYDiskCache

作者: AppleTTT | 来源:发表于2017-05-03 18:09 被阅读122次

    YYDiskCache

    YYDiskCache 是一个基于SQLite和文件系统的线程安全的键值存储cache

    YYDiskCache is a thread-safe cache that stores key-value pairs backed by SQLite
    and file system (similar to NSURLCache's disk cache).

    Feature
    1. YYDiskCache使用LRU算法去移除数据;
    2. YYDiskCache可以控制系统开销,缓存数量和缓存时间;
    3. YYDiskCache是可配置的,并可以在没有足够的磁盘空间的时候自动删除一些数据;
    4. YYDiskCache会自动帮我们决定每个数据是用sqlite存储或者是file存储更好一些;

    我们也可以编译最新的sqlite版本来替换iOS系统的libsqlite3.dylib,这样的话,存取速度可以达到2-4倍的提升;

    YYDiskCache里面的大多数实现都是依靠YYKVStorage,所以如果你看了YYKVStorage的话,你会发现他们有很多类似的API,在实际使用中,我们并不需要直接使用YYKVStorage,而是使用更高层封装的YYDiskCache就行了。

    Attributes
    attribute
    1. name: cache的名字,默认为nil;
    2. path: 存储的路径(read-only);
    3. inlineThreshold: 一个阈值;如果存储的数据size大于这个值,则以文件的形式存储,否则用sqlite存储;默认为20KB;
    4. NSData *(^customArchiveBlock)(id object): 如果此block不为空的话,那么就会用此block来archive数据而不是用默认的NSKeyedArchiver,这样就可以让那些不支持NSCoding协议的类去archive了;
    5. id (^customUnarchiveBlock)(NSData *data);: 解码,同上;
    6. NSString *(^customFileNameBlock)(NSString *key): 当数据要存储为文件时,这个block会被调用并根据key来生成一个文件名,如果为空,则使用key的MD5来作为默认的文件名;
    7. errorLogsEnabled: 是否允许打印日志;
    limit attribute
    1. countLimit: 数据数量限制,默认为NSUIntegerMax;这并不是一个严格的限制(并不是一个一个的去删除,而是有一个基数,每次删除基数个数据);如果cache中的数量超过了这个数字,则会在后台线程中删除一部分数据;
    2. costLimit: 磁盘的total开销超过此限制,则开始在后台线程中删除部分数据,这也不是一个严格的限制;默认为NSUIntegerMax
    3. ageLimit: 时间限制,并不是严格的限制;超过此日期的数据会被在后台线程中删除;默认为DBL_MAX;
    4. freeDiskSpaceLimit: 剩余空间限制,并不严格的限制;当剩余空间小于此限制时,部分数据会在后台被删除;默认为0;
    5. autoTrimInterval: 自动去检查以上这些限制的时间间隔;默认为60s;
    全局定义
    宏定义

    #define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
    #define Unlock() dispatch_semaphore_signal(self->_lock)
    使用两句宏定义来进行锁操作,来保证数据的安全性,其中self->_lock是私有成员(一个自定义的并行队列);
    在YYMemoryCache中,YY都是用的pthread_mutex_lock来进行加锁以保证数据的安全性,不知道他为什么不用同一种,以后要对几种锁也研究下,写个博客啥的,这都是后话了。

    全局变量
    1. static const int extended_data_key
      用来在runtime期间添加成员变量extended_data_key,来获取extended data;

    2. static NSMapTable *_globalInstances;
      全局的映射实例,是为了获取所有实例的weak引用的;
      关于NSMapTable的具体内容,大家可以看官方文档或者是这边博客NSMapTable: 不只是一个能放weak指针的 NSDictionary

    3. static dispatch_semaphore_t _globalInstancesLock;
      全局实例的锁;
      还是稍微讲解下信号量dispatch_semaphore_t

    关于信号量,最重要的是一下三个函数;
    1、 dispatch_semaphore_t dispatch_semaphore_create(long value)
        * 初始化一个信号量,其中value表示初始化时信号量的值(有时候你可以理解为当前可用资源剩余个数);
    2、 long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
        * 此函数会先查看信号量dsema的值,如果是大于0的则在执行此函数语句的地方继续执行下面的语句并将dsema信号量值-1;
        * 如果当前dsema的值小于等于0,则会阻塞当前线程,并等待到timeout;
            * 在等待期间若该函数所处线程获取了信号量,即dsema的值大于0了,那么就会继  续执行该函数下面的语句;
            * 在等待期间没有获得信号量,那么在timeout时间之后,就会继续执行该函数之后的语句了;
        * 返回值为0表示在timeout时间内获取到了信号量,不为0则表示经过了timeout时间
    3、 long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
        * 将dsema的信号量做+1处理;
        * 如果dsema之前的信号量小于0,则在返回之前会唤醒一个正在等待的线程去处理改信号量
        * 返回值为0时表示当前并没有线程等待其处理的信号量;
        * 返回值不为0表示当前有一个或多个线程等待其处理的信号量,改函数已经唤醒了一个线程去处理改信号量了;
    
    静态函数
    1. static int64_t _YYDiskSpaceFree()
      使用NSFileManager获取系统剩余(NSFileSystemFreeSize)的size,返回此size(in bytes)

    2. static NSString *_YYNSStringMD5(NSString *string)
      给指定字符串生成MD5串;

      • 使用dataUsingEncoding方法将string进行NSUTF8StringEncoding编码后转为NSData;
      • 初始化一个CC_MD5_DIGEST_LENGTH(16 digest length in bytes)长度的C数组result;
      • 使用extern unsigned char *CC_MD5(const void *data, CC_LONG len, unsigned char *md)函数,根据生成的data来填充result;
      • 将数组转为字符串并返回;
    3. static void _YYDiskCacheInitGlobal()
      全局变量的初始化工作,以单例的形式对全局变量_globalInstancesLock_globalInstances进行初始化;
      _globalInstancesLock初始化为dispatch_semaphore_create(1);
      _globalInstances初始化为一个对key强引用,对value弱引用,容量为0的NSMapTable;

    4. static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path)
      获取 YYDiskCache 的实例;

      • 如果path为长度为0,直接返回nil;
      • 使用_YYDiskCacheInitGlobal()去做初始化工作;
      • 使用dispatch_semaphore_wait()进行加锁操作;
      • _globalInstances中取出 path 对应的 value;
      • 使用dispatch_semaphore_signal进行解锁操作;
      • 返回取出的value;
    5. static void _YYDiskCacheSetGlobal(YYDiskCache *cache)

      • 将cache缓存到全局变量_globalInstances
      • 使用信号量进行加锁,解锁操作;
    implementation实现
    私有成员
    1. YYKVStorage *_kv: 实际进行操作的YYKVStorage实例;
    2. dispatch_semaphore_t _lock: 用来加锁的信号量;
    3. dispatch_queue_t _queue: 自定义的并行队列,所有对外的接口中异步执行的block都在此队列中执行;
    私有方法
    1. - (void)_trimRecursively

      • 对缓存中的数据进行递归修剪,即使用给定的_autoTrimInterval来定期对数据进行cost, count, age, freeDiskSpace的检查;
      • 使用dispatch_after函数在优先级为Low的全局队列中执行方法_trimInBackground
      • 递归调用_trimRecursively;
    2. - (void)_trimInBackground

      • _queue对了中异步执行了修剪操作;
      • 使用定义好的宏Lock()Unlock()进行加锁操作执行方法_trimToCost, _trimToCount, _trimToAge, _trimToFreeDiskSpace;
    3. - (void)_trimToCost:(NSUInteger)costLimit

    4. - (void)_trimToAge:(NSTimeInterval)ageLimit

    5. - (void)_trimToCount:(NSUInteger)countLimit
      这三个方法实际都是调用的YYKVStorage里面的方法执行的,大家可以看下YYKVStorage的博客去看具体的实现过程;

    6. - (void)_trimToFreeDiskSpace:(NSUInteger)targetFreeDiskSpace
      实际是通过YYKVStorage获取总的items的开销,然后对比_YYDiskSpaceFree()获取的总的内存空间,然后使用差值调用_trimToCost方法来释放空间;

    7. - (NSString *)_filenameForKey:(NSString *)key
      若已经定义好_customFileNameBlock则使用block来获取key对应的filename来返回,否则使用默认的_YYNSStringMD5方法获取到key对应的filename之后返回;

    8. - (void)_appWillBeTerminated
      使用宏定义的锁加锁后,将私有变量_kv置空;

    public方法
    Initializer

    默认的初始化方法-init-new都被UNAVAILABLE_ATTRIBUTE(不可用);
    指定的初始化方法如下:

    1. - (nullable instancetype)initWithPath:(NSString *)path
      path必须为完整的路径名,如果已经存在此path的实例,则直接返回此实例,否则创建实例并返回;
      实际调用的是- (nullable instancetype)initWithPath:(NSString *)path inlineThreshold:(NSUInteger)threshold方法,指定threshold为1024 * 20;

    2. - (nullable instancetype)initWithPath:(NSString *)path inlineThreshold:(NSUInteger)threshold
      指定的初始化方法(NS_DESIGNATED_INITIALIZER),如果已经存在此path的实例,则直接返回此实例;

      • threshold:阈值,用来给属性inlineThreshold赋值,一旦初始化后,不要再次改变此值;
      • 首先会从_YYDiskCacheGetGlobal函数中查找是否有此path对应的YYDiskCache,有就直接返回,没有就进行下面的操作;
      • 根据threshold的值会自动定义YYDiskCache的type;
      • 使用默认值初始化各成员变量;
      • 调用_trimRecursively开始定时检查cache;
      • 调用_YYDiskCacheSetGlobal将self存放在全局的_globalInstances中;
      • 添加UIApplicationWillTerminateNotification的通知,在APP被关闭时将_kv置为nil;
    Access Methods
    1. - (BOOL)containsObjectForKey:(NSString *)key;

      • 使用宏定义加锁后,使用YYKVStorage的itemExistsForKey方法来查找是否包含此key;
      • 此方法会阻塞当前线程直到读取完数据;
      • 后面的其他会阻塞当前线程的方法与此方法类似,均是使用YYKVStorage中的API来执行具体的操作,使用Lock()Unlock()进行加锁和解锁;
    2. - (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block;

      • _queue中异步执行containsObjectForKey方法,并在此之后执行block;
      • 后面其他非阻塞当前线程的方法与此方法类似,均是在_queue中执行对应的阻塞当前线程的方法然后执行block的;
    3. - (nullable id<NSCoding>)objectForKey:(NSString *)key;

      • 返回指定key对用的数据,此方法会阻塞当前线程,直到读取完数据;

      如果存在customUnarchiveBlock,则会使用此block去做解档操作

    4. - (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id<NSCoding> _Nullable object))block;
      此方法会立即返回给block,并在后台线程中调用block

    5. - (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;

      • 如果object为nil,则调用removeObjectForKey方法;
      • 如果不为空则阻塞当前线程直达写入完成为止;

      如果存在_customArchiveBlock,则会使用此block去做归档操作

    6. - (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(void(^)(void))block;
      此方法会立即返回,并在后台线程中执行block;

    7. - (void)removeObjectForKey:(NSString *)key;
      阻塞当前线程,知道删完key对应的数据为止;

    8. - (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block;
      立即返回,后台线程中执行block;

    9. - (void)removeAllObjects;
      阻塞当前线程,知道删完所有数据为止;

    10. - (void)removeAllObjectsWithBlock:(void(^)(void))block;
      立即返回,后台执行删除任务;

    11. - (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress endBlock:(nullable void(^)(BOOL error))end;
      立即返回,并在每次删除一些数据后调用progress,删除完后调用end;

    12. - (NSInteger)totalCount;
      阻塞当前线程,计算完毕后返回cache中数据的总数;

    13. - (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block;
      不阻塞,立即返回,block在后台执行

    14. - (NSInteger)totalCost;
      - (NSInteger)totalCount;

    15. - (void)totalCostWithBlock:(void(^)(NSInteger totalCost))block;
      - (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block;

    Trim
    1. - (void)trimToCount:(NSUInteger)count;
      阻塞当前线程;根据LRU来remove objects;

    2. - (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block;
      不阻塞当前线程,根据LRU remove objects

    3. - (void)trimToCost:(NSUInteger)cost;
      - (void)trimToCount:(NSUInteger)count;

    4. - (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block;
      - (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block;

    5. - (void)trimToAge:(NSTimeInterval)age;
      - (void)trimToCount:(NSUInteger)count;

    1. - (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block;
      - (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block;
    Extended Data
    1. + (nullable NSData *)getExtendedDataFromObject:(id)object;
      类方法;runtime添加的类属性,设置指定object的extendedData;

    2. + (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object;
      类方法;获取指定object的extend数据;

    参考文章
    1. 关于dispatch_semaphore的使用
    2. isaced的博客
    相关阅读
    1. YYCache源码阅读一YYStorage
    2. YYCache源码阅读一YYMemoryCache

    相关文章

      网友评论

          本文标题:YYCache源码阅读一YYYDiskCache

          本文链接:https://www.haomeiwen.com/subject/lhlltxtx.html