iOS源码(三)YYCache

注:本文最好是配着代码一起阅读,如果我把代码加到文章里面,篇幅太大。

YYCache的一些基本方法:
//根据名称或者路径获取YYCache对象

  • (instancetype)cacheWithName:(NSString *)name;
  • (instancetype)cacheWithPath:(NSString *)path;
    //判断缓存是否存在
  • (BOOL)containsObjectForKey:(NSString *)key;
  • (void)containsObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key, BOOL contains))block;
    //读取缓存
  • (id<NSCoding>)objectForKey:(NSString *)key;
    (void)objectForKey:(NSString *)key withBlock:(void (^)(NSString *key, id<NSCoding> object))block;
    //存入对象
  • (void)setObject:(id<NSCoding>)object forKey:(NSString *)key;
  • (void)setObject:(id<NSCoding>)object forKey:(NSString *)key withBlock:(void (^)(void))block;
    //移除缓存
  • (void)removeObjectForKey:(NSString *)key;
  • (void)removeObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key))block;
  • (void)removeAllObjects;
  • (void)removeAllObjectsWithBlock:(void(^)(void))block;
  • (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress
    endBlock:(void(^)(BOOL error))end;

根据名称或者路径获取yycache对象:
如果是传入名称,就获取Documents目录路径和传入的名称进行拼接来生成缓存路径,如果是传入的路径,则开始进行缓存初始化的操作。(错误检测:传入的path是否为空)
1、YYDiskCache的初始化(传入路径),2、根据路径获取名称,3、初始化YYMemoryCache。4、赋值

YYDiskCache的初始化:初始化有路径和一个20kb的数值类型(方法需要传入缓存路径和缓存阈值threshold参数。在作者设计思路文章中分析到,超过20k数据使用文件缓存读写快,而低于20k数据使用数据库读写比较快),初始化方法里面:1、YYDiskCache的初始化,2、YYKVStorageType的初始化,3、YYKVStorage的初始化,4、方法- (void)_trimRecursively;(在初始化的时候调用_trimRecursively方法每隔60s时间检测一下缓存数据大小是否超过容量。),5、创建程序终止的监听。(错误检测:以上每一个初始化如果依靠返回的值的话,如果为空则直接return nil。)

上述1中,YYDiskCache于_YYDiskCacheGetGlobal,_YYDiskCacheGetGlobal首先单例初始化一个static NSMapTable *_globalInstances;和static dispatch_semaphore_t _globalInstancesLock; 然后在_globalInstances里取值cache,取值过程利用信号量(_globalInstancesLock)来保证线程安全。然后返回cache赋值给YYDiskCache对象。

上述2:根据传入的数值的大小来初始化存储类型,三种类型(YYKVStorageTypeFile = 0,
YYKVStorageTypeSQLite = 1,YYKVStorageTypeMixed = 2,)

上述3:YYKVStorage的初始化会传入路径和存储类型,进入初始化函数(错误判断:会判断传入的路径和类型。),在初始化函数里会根据传入名称,数据名称和废弃的垃圾名称来创建NSFileManager文件对象。然后会判断数据库是否打开和是否初始化过(算是一种容错的操作)。然后进入方法_fileEmptyTrashInBackground(如果上次失败,清空垃圾,并且会将清除操作放在异步队列里进行)

上述5:程序终止的监听,接收到的时候会把YYKVStorage对象赋值为nil。

存入对象:双缓存
先存内存,然再存磁盘。
存内存:模拟NSCache的方法,传入对象,key值然后封装成传入对象,key值,大小cost。- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost; 方法内部(错误判断:key为空则返回nil,对象为空则根据key移除存储对象),存储过程中利用pthread_mutex_lock互斥所保证线程安全,存储过程:主要是模拟lru,利用CFDictionaryGetValue方法从_YYLinkedMap *_lru;(对象定义看注解①)里面的字典对象根据key值遍历查找,结果返回并赋值给_YYLinkedMapNode *node(对象定义看注解②),如果找到则重新赋值,未找到则创建node并重新赋值,然后放置lru的链表头部。然后进行缓存大小是否超过限制的判断,如果超过则异步进行清理操作(- (void)_trimToCost:(NSUInteger)costLimit;(详情可看注解③)),然后再进行判断。(内存的清理可以设置成在主线程里清除或者异步清除)。

存磁盘:老规矩,先来一手错误的判断,判断key值和对象的值是否为空,然后根据YYKVStorage里的kv对象来进行操作([_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];),这个过程加锁,依旧是判断传入的值是否错误,然后根据存储类型来进行sqlite还是文件磁盘的存储。

读取缓存:双缓存
老规矩,先从YYMemoryCache内存里读取,内存读取方法里面(错误判断传入的key是否为空)用pthread_mutex_lock互斥锁来保证线程安全,然后用CFDictionaryGetValue方法来遍历lru里面的链表,如果内存找到先把找到的对象的节点放于表头然后返回,如果未找到则进入磁盘查找。

移除缓存
依旧是错误的判断以及加锁,如果链表尾部或者是根据CFDictionaryGetValue找到lru相应的节点并删除,还有就是获取的对象在主线程释放还是异步释放的判断。

一些解释:
NSMapTable: NSMapTable和NSDictionary相对应,相对于NSDictionary/NSMutableDictionary,NSMapTable有如下的特征: NSDictionary/NSMutableDictionary会copy对应的key,强引用相应的value。 NSMapTable是可变的,没有一个不变的类与其对应。 NSMapTable可以对其key和value弱引用,在这种情况下当key或者value被释放的时候,此entry会自动从NSMapTable中移除。 NSMapTable在加入一个(key,value)的时候,可以对其value设置为copy。 NSMapTable可以包含任意指针,使用指针去做相等或者hashing检查。
dispatch_semaphore_t:信号量,用于线程同步。

@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly
NSUInteger _totalCost;
NSUInteger _totalCount;
_YYLinkedMapNode *_head; // MRU, do not change it directly
_YYLinkedMapNode *_tail; // LRU, do not change it directly
BOOL _releaseOnMainThread;
BOOL _releaseAsynchronously;
}

@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
id _key;
id _value;
NSUInteger _cost;
NSTimeInterval _time;
}
@end

注解③:- (void)_trimToCost:(NSUInteger)costLimit;
流程:同样的互斥锁加锁,并用一个bool值finsh来判断是否完成清理然后解锁。清理的过程主要是移除lru链表尾部的对象(首先是移除尾部,然后把尾部的对象放在一个可变数组里,最后去判断在主线程里清理还是异步清理,并在串行队列里面去释放这个可变数组里面的对象)

关于
_YYLinkedMapNode *node = [_lru removeTailNode];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
[node class]; 这种在queue上调用对象的方法
这种写法的解释:应该是node在执行完这个方法后就出了作用域了,reference会减1,但是此时node不会被dealloc,因为block 中retain了node,使得node的reference count为1,当执完block后,node的reference count又-1,此时node就会在block对应的queue上release了。

YYDiskCache里用dispatch_semaphore_wait二不是用OSSpinLockLock的原因:DiskCache 锁占用时间可能会比较长,如果用 SpinLock 会在锁存在竞争时占用大量 CPU 资源。

特定线程释放资源的解释:避免了过多线程导致的性能问题。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 今天开始分析YYCache 包含的文件类 YYCache YYMemoryCache YYDiskCache YY...
    充满活力的早晨阅读 771评论 4 1
  • 从 YYCache 源码 Get 到如何设计一个优秀的缓存 来源:Lision 前言 iOS 开发中总会用到各种缓...
    今天lgw阅读 5,975评论 1 22
  • YYCache是用于Objective-C中用于缓存的第三方框架。此文主要用来讲解该框架的实现细节,性能分析、设计...
    JonesCxy阅读 547评论 0 2
  • 概述 上一篇主要讲解了YYCache的文件结构,分析了YYCache类的相关方法,本章主要分析内存缓存类YYMem...
    egoCogito_panf阅读 3,142评论 2 12
  • 前言 日常的iOS开发过程中,经常会用到缓存,但是什么样的缓存才能被叫做优秀的缓存,或者说优秀的缓存应该具备哪些特...
    雨润听潮阅读 2,446评论 0 2