关于YYCache的简单总结。
阅读YYCache源码有3遍了,自我感觉“需要理解”的部分掌握的差不多了,做个记录。
一、接口 API 类似字典,比较容易理解。
1.初始化 1 2 3 4 5 6 7 8 9 @property (copy , readonly ) NSString *name;@property (strong , readonly ) YYMemoryCache *memoryCache;@property (strong , readonly ) YYDiskCache *diskCache;- (nullable instancetype )initWithName:(NSString *)name; - (nullable instancetype )initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER ; + (nullable instancetype )cacheWithName:(NSString *)name; + (nullable instancetype )cacheWithPath:(NSString *)path; - (instancetype )init UNAVAILABLE_ATTRIBUTE; + (instancetype )new UNAVAILABLE_ATTRIBUTE;
2.是否存在 1 2 - (BOOL )containsObjectForKey:(NSString *)key; - (void )containsObjectForKey:(NSString *)key withBlock:(nullable void (^)(NSString *key, BOOL contains))block;
3.查询 1 2 - (nullable id <NSCoding >)objectForKey:(NSString *)key; - (void )objectForKey:(NSString *)key withBlock:(nullable void (^)(NSString *key, id <NSCoding > object))block;
4.设值 1 2 - (void )setObject:(nullable id <NSCoding >)object forKey:(NSString *)key; - (void )setObject:(nullable id <NSCoding >)object forKey:(NSString *)key withBlock:(nullable void (^)(void ))block;
5.移除 1 2 3 4 5 - (void )removeObjectForKey:(NSString *)key; - (void )removeObjectForKey:(NSString *)key withBlock:(nullable void (^)(NSString *key))block; - (void )removeAllObjects; - (void )removeAllObjectsWithBlock:(void (^)(void ))block; - (void )removeAllObjectsWithProgressBlock:(nullable void (^)(int removedCount, int totalCount))progress endBlock:(nullable void (^)(BOOL error))end;
二、实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 @implementation YYCache - (instancetype ) init { NSLog (@"Use \"initWithName\" or \"initWithPath\" to create YYCache instance." ); return [self initWithPath:@"" ]; } - (instancetype )initWithName:(NSString *)name { if (name.length == 0 ) return nil ; NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains (NSCachesDirectory , NSUserDomainMask , YES ) firstObject]; NSString *path = [cacheFolder stringByAppendingPathComponent:name]; return [self initWithPath:path]; } - (instancetype )initWithPath:(NSString *)path { if (path.length == 0 ) return nil ; YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path]; if (!diskCache) return nil ; NSString *name = [path lastPathComponent]; YYMemoryCache *memoryCache = [YYMemoryCache new]; memoryCache.name = name; self = [super init]; _name = name; _diskCache = diskCache; _memoryCache = memoryCache; return self ; } + (instancetype )cacheWithName:(NSString *)name { return [[self alloc] initWithName:name]; } + (instancetype )cacheWithPath:(NSString *)path { return [[self alloc] initWithPath:path]; } - (BOOL )containsObjectForKey:(NSString *)key { return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key]; } - (void )containsObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key, BOOL contains))block { if (!block) return ; if ([_memoryCache containsObjectForKey:key]) { dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ block(key, YES ); }); } else { [_diskCache containsObjectForKey:key withBlock:block]; } } - (id <NSCoding >)objectForKey:(NSString *)key { id <NSCoding > object = [_memoryCache objectForKey:key]; if (!object) { object = [_diskCache objectForKey:key]; if (object) { [_memoryCache setObject:object forKey:key]; } } return object; } - (void )objectForKey:(NSString *)key withBlock:(void (^)(NSString *key, id <NSCoding > object))block { if (!block) return ; id <NSCoding > object = [_memoryCache objectForKey:key]; if (object) { dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ block(key, object); }); } else { [_diskCache objectForKey:key withBlock:^(NSString *key, id <NSCoding > object) { if (object && ![_memoryCache objectForKey:key]) { [_memoryCache setObject:object forKey:key]; } block(key, object); }]; } } - (void )setObject:(id <NSCoding >)object forKey:(NSString *)key { [_memoryCache setObject:object forKey:key]; [_diskCache setObject:object forKey:key]; } - (void )setObject:(id <NSCoding >)object forKey:(NSString *)key withBlock:(void (^)(void ))block { [_memoryCache setObject:object forKey:key]; [_diskCache setObject:object forKey:key withBlock:block]; } - (void )removeObjectForKey:(NSString *)key { [_memoryCache removeObjectForKey:key]; [_diskCache removeObjectForKey:key]; } - (void )removeObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key))block { [_memoryCache removeObjectForKey:key]; [_diskCache removeObjectForKey:key withBlock:block]; } - (void )removeAllObjects { [_memoryCache removeAllObjects]; [_diskCache removeAllObjects]; } - (void )removeAllObjectsWithBlock:(void (^)(void ))block { [_memoryCache removeAllObjects]; [_diskCache removeAllObjectsWithBlock:block]; } - (void )removeAllObjectsWithProgressBlock:(void (^)(int removedCount, int totalCount))progress endBlock:(void (^)(BOOL error))end { [_memoryCache removeAllObjects]; [_diskCache removeAllObjectsWithProgressBlock:progress endBlock:end]; } - (NSString *)description { if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)" , self .class, self , _name]; else return [NSString stringWithFormat:@"<%@: %p>" , self .class, self ]; } @end
整体来看还是比较容易理解的,YYCache 整合了内存缓存和磁盘缓存,主要做了这些事:
初始化YYCache实例,要求缓存路径要合理。
查询缓存对象先从内存缓存中查找,如果没有,再从磁盘缓存中查找。当磁盘缓存中有而内存缓存中没有的时候,把取得的缓存对象保存在内存缓存中。
缓存对象的存取和移除,内存缓存和磁盘缓存两者保持同步。
1.YYMemoryCache 基本属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @property (nullable, copy) NSString *name; @property (readonly) NSUInteger totalCount; @property (readonly) NSUInteger totalCost; @property NSUInteger countLimit; @property NSUInteger costLimit; @property NSTimeInterval ageLimit; @property NSTimeInterval autoTrimInterval; @property BOOL shouldRemoveAllObjectsOnMemoryWarning; @property BOOL shouldRemoveAllObjectsWhenEnteringBackground; @property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache); @property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache); @property BOOL releaseOnMainThread; @property BOOL releaseAsynchronously;
类似于NSCache,提供包括缓存数量、缓存花费、缓存时间的管理。除此之外,当接收到内存警告时(shouldRemoveAllObjectsOnMemoryWarning)
或者 App 进入到后台时(shouldRemoveAllObjectsWhenEnteringBackground)
可以选择释放缓存对象。而对缓存对象释放也可以进行控制,比如可以选择在主线程释放(releaseOnMainThread)
或者异步释放(releaseAsynchronously)
。
存取接口 1 2 3 4 5 6 7 8 9 10 - (BOOL )containsObjectForKey:(id )key; - (nullable id )objectForKey:(id )key; - (void )setObject:(nullable id )object forKey:(id )key; - (void )setObject:(nullable id )object forKey:(id )key withCost:(NSUInteger )cost; - (void )removeObjectForKey:(id )key; - (void )removeAllObjects; - (void )trimToCount:(NSUInteger )count; - (void )trimToCost:(NSUInteger )cost; - (void )trimToAge:(NSTimeInterval )age;
除了基本的存取方法之外,YYMemoryCache 还暴露了三个移除缓存对象的方法。 trimToCount:根据限制的数量(countLimit)进行移除缓存对象操作,直到满足数量限制要求。 trimToCost:根据限制的花费(costLimit)进行移除缓存对象操作,直到满足花费限制要求。 trimToAge:根据缓存对象的过期时间(ageLimit)进行移除缓存对象操作,直到满足要求。
具体实现 在具体实现中,作者考虑了以下要求:
缓存对象的存取。要保证效率就要求存取时间复杂度最好是O(1)。
缓存对象的移除。要保证能按照 cost、age、count
等条件对所有不符合要求的对象进行移除。
线程安全。需要加锁。 第一条:要求时间复杂度是 O(1),那就可以采用哈希表、字典等。作者使用了字典,且是效率更高的CFMutableDictionaryRef
。 第二条:记录 cost、age、count
等属性,肯定需要对缓存对象进行包装。再者需要考虑LRU(Least Recently Used),就需要保证顺序。而要使得字典中的对象(value)有顺序,必须有一个指向其他对象的指针(属性)。可以使用双向链表包装缓存对象。 第三条:存取的线程安全,使用GCD线程锁。
结点 1 2 3 4 5 6 7 8 9 10 @interface _YYLinkedMapNode : NSObject { @package __unsafe_unretained _YYLinkedMapNode *_prev ; __unsafe_unretained _YYLinkedMapNode *_next ; id _key ; id _value ; NSUInteger _cost ; NSTimeInterval _time ; } @end
链表的结点中保存着key 和 value,这是对缓存对象的包装。_cost和_time记录着缓存对象的花费和过期时间。而使用__unsafe_unretained
修饰的_prev和_next则分别指向前一个对象和后一个对象。由于这些结点已经被字典持有了,所以直接使用__unsafe_unretained
修饰,不必再增加它的引用计数,提高效率。这一点与隐式参数self
很像,self
其实也是使用__unsafe_unretained
修饰的。
链表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @interface _YYLinkedMap : NSObject { @package CFMutableDictionaryRef _dic ; NSUInteger _totalCost ; NSUInteger _totalCount ; _YYLinkedMapNode *_head ; _YYLinkedMapNode *_tail ; BOOL _releaseOnMainThread ; BOOL _releaseAsynchronously ; } - (void)insertNodeAtHead:(_YYLinkedMapNode *)node; - (void)bringNodeToHead:(_YYLinkedMapNode *)node; - (void)removeNode:(_YYLinkedMapNode *)node; - (_YYLinkedMapNode *)removeTailNode; - (void)removeAll; @end
一个 YYMemoryCache
对象有一个链表。这个链表使用_dic
保存着所有包装好的缓存对象(_YYLinkedMapNode
),记录着总的花费(_totalCost
)和总的数量(_totalCount
)。当然,还使用_head
指着链表的头指针,使用_tail
指着链表的尾指针。_releaseOnMainThread
和_releaseAsynchronously
用于设置对缓存对象释放操作的选项:主线程释放或者异步释放。
_YYLinkedMap
暴露出的五个方法很清晰地表明它的作用:每次当缓存取到某个对象时,把它置在头结点的位置。这样随着时间的推移,很轻松地使得那些不经常使用的对象处在链表的后端,经常使用的对象处在链表的前端,这样就实现了 LRU
。
操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 - (void)insertNodeAtHead:(_YYLinkedMapNode *)node { CFDictionarySetValue (_dic, (__bridge const void *)(node ->_key ), (__bridge const void *)(node )); _totalCost += node ->_cost ; _totalCount++; if (_head) { node ->_next = _head; _head->_prev = node ; _head = node ; } else { _head = _tail = node ; } } - (void )bringNodeToHead:(_YYLinkedMapNode *)node { if (_head == node ) return ; if (_tail == node ) { _tail = node ->_prev ; _tail->_next = nil; } else { node ->_next- >_prev = node ->_prev ; node ->_prev- >_next = node ->_next ; } node ->_next = _head; node ->_prev = nil; _head->_prev = node ; _head = node ; } - (void )removeNode:(_YYLinkedMapNode *)node { CFDictionaryRemoveValue (_dic, (__bridge const void *)(node ->_key )); _totalCost -= node ->_cost ; _totalCount--; if (node ->_next ) node ->_next- >_prev = node ->_prev ; if (node ->_prev ) node ->_prev- >_next = node ->_next ; if (_head == node ) _head = node ->_next ; if (_tail == node ) _tail = node ->_prev ; } - (_YYLinkedMapNode *)removeTailNode { if (!_tail) return nil; _YYLinkedMapNode *tail = _tail; CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key)); _totalCost -= _tail->_cost; _totalCount--; if (_head == _tail) { _head = _tail = nil; } else { _tail = _tail->_prev; _tail->_next = nil; } return tail; }
对结点的插入、移除、调整位置,是数据结构的基础操作。令人怀念!
_releaseOnMainThread
和_releaseAsynchronously
这两个选项的实现也很简单。作者自己维护了一个队列:
1 2 3 static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue () { return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0 ); }
移除的时候有所判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if (CFDictionaryGetCount (_dic) > 0 ) { CFMutableDictionaryRef holder = _dic; _dic = CFDictionaryCreateMutable (CFAllocatorGetDefault (), 0 , &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (_releaseAsynchronously) { dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); dispatch_async (queue, ^{ CFRelease (holder); }); } else if (_releaseOnMainThread && !pthread_main_np()) { dispatch_async (dispatch_get_main_queue(), ^{ CFRelease (holder); }); } else { CFRelease (holder); } }
根据线程、是否异步决定具体的逻辑,把release操作放到相应线程中。这里使用一个临时变量holder,很是巧妙。
核心操作 1 2 3 4 5 @implementation YYMemoryCache { pthread_mutex_t _lock _YYLinkedMap *_lru dispatch_queue_t _queue }
从这里可以看出YYMemoryCache
使用pthread_mutex_t
保证线程安全。
最关键的还是对无效缓存对象的释放,以 count
为例,当缓存的对象数量超过了 count
限制,就需要对链表后端不常使用的缓存对象进行移除操作,直到满足 count
限制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 - (void)_trimToCount :(NSUInteger)countLimit { BOOL finish = NO; pthread_mutex_lock(&_lock ); if (countLimit == 0 ) { [_lru removeAll]; finish = YES; } else if (_lru ->_totalCount <= countLimit) { finish = YES; } pthread_mutex_unlock(&_lock ); if (finish) return; NSMutableArray *holder = [NSMutableArray new]; while (!finish) { if (pthread_mutex_trylock(&_lock ) == 0 ) { if (_lru ->_totalCount > countLimit) { _YYLinkedMapNode *node = [_lru removeTailNode]; if (node) [holder addObject:node]; } else { finish = YES; } pthread_mutex_unlock(&_lock ); } else { usleep(10 * 1000 ); } } if (holder.count ) { dispatch_queue_t queue = _lru ->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); dispatch_async(queue, ^{ [holder count ]; }); } }
这个方法做了什么?
使用pthread_mutex_lock(&_lock);pthread_mutex_unlock(&_lock);
对操作加锁解锁来保证线程安全。
对参数 countLimit 判断: 如果countLimit为0,也就是说缓存数量限制为0,那移除所有缓存对象就是了。如果当前所缓存的对象数量小于countLimit,那说明满足数量限制要求,就不需要移除操作了。
作者创建一个可变字典holder,当不满足countLimit限制要求的时候,对链表尾结点进行移除操作,并把这个尾结点添加到holder中持有。当然,这里有加锁操作。这个 while 循环结束,所有多余的缓存对象就在holder中了。 4.对holder中所有元素进行 release 操作。 其他的如:
1 2 - (void) _trimToAge:(NSTimeInterval) ageLimit; - (void) _trimToCost:(NSUInteger) costLimit
操作同理。
内存警告 内存警告语 App 进入后台时释放缓存对象的操作,作者接受了系统通知,直接处理即可。
1 2 [[NSNotificationCenter defaultCenter] addObserver :self selector :@selector (_appDidReceiveMemoryWarningNotification ) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];[[NSNotificationCenter defaultCenter] addObserver :self selector :@selector (_appDidEnterBackgroundNotification ) name:UIApplicationDidEnterBackgroundNotification object:nil];
存取实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - (BOOL )containsObjectForKey:(id )key { if (!key) return NO ; pthread_mutex_lock(&_lock); BOOL contains = CFDictionaryContainsKey (_lru->_dic, (__bridge const void *)(key)); pthread_mutex_unlock(&_lock); return contains; } - (id )objectForKey:(id )key { if (!key) return nil ; pthread_mutex_lock(&_lock); _YYLinkedMapNode *node = CFDictionaryGetValue (_lru->_dic, (__bridge const void *)(key)); if (node) { node->_time = CACurrentMediaTime (); [_lru bringNodeToHead:node]; } pthread_mutex_unlock(&_lock); return node ? node->_value : nil ; } - (void )setObject:(id )object forKey:(id )key { [self setObject:object forKey:key withCost:0 ]; }
对缓存对象的读取自然是根据 key 读取到字典中对应的 value,这个 value 是个结点(_YYLinkedMapNode
),再取出这个结点的value 属性,便是最原始的缓存对象了:node->_value
。
2.关键点 1.字典CFMutableDictionaryRef的使用
1 2 3 4 5 6 7 8 声明: CFMutableDictionaryRef _dic; 创建: _dic = CFDictionaryCreateMutable (CFAllocatorGetDefault (), 0 , &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 设值: CFDictionarySetValue (_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node)); 取值: CFDictionaryGetValue (_lru->_dic, (__bridge const void *)(key)); 移除: CFDictionaryRemoveValue (_dic, (__bridge const void *)(node->_key)); 获取数量: CFDictionaryGetCount (_dic); 是否存在: CFDictionaryContainsKey (_lru->_dic, (__bridge const void *)(key)); 释放: CFRelease (_dic);
2.容器中对象销毁控制
“对象的销毁虽然消耗资源不多,但累积起来也是不容忽视的。通常当容器类持有大量对象时,其销毁时的资源消耗就非常明显。同样的,如果对象可以放到后台线程去释放,那就挪到后台线程去…”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 { NSArray *holder = tmp; tmp = [NSMutableArray array]; if (holder.count > 0 ) { dispatch_async (dispatch_get_global_queue(0 , 0 ), ^{ [holder count]; }); } } { id obj = tmp[2 ]; NSMutableArray *holder = [NSMutableArray array]; [holder addObject:obj]; [tmp removeObject:obj]; if (holder.count > 0 ) { dispatch_async (dispatch_get_global_queue(0 , 0 ), ^{ [holder count]; }); } }
3.线程安全的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { pthread_mutex_lock(&_lock ); BOOL releaseAsynchronously = _lru ->_releaseAsynchronously ; pthread_mutex_unlock(&_lock ); return releaseAsynchronously; } { pthread_mutex_lock(&_lock ); _lru ->_releaseAsynchronously = releaseAsynchronously; pthread_mutex_unlock(&_lock ); }
4.pthread_mutex_lock
使用
1 2 3 4 5 6 7 8 9 10 11 声明锁: pthread_mutex_t _lock ; 创建锁: pthread_mutex_init(&_lock , NULL); 加锁: pthread_mutex_lock(&_lock ); 解锁: pthread_mutex_unlock(&_lock ); 尝试加锁: if (pthread_mutex_trylock(&_lock ) == 0 ) { pthread_mutex_unlock(&_lock ); } else { usleep(10 * 1000 ); } 销毁锁: pthread_mutex_destroy(&_lock );
5.if-else 单句
1 2 if (_name) return nil ;else return nil ;
3.YYDiskCache YYDiskCache主要调用了YYKVStorage的接口,并提供对外 API。
五个函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 /// Free disk space in bytes. static int64_t _YYDiskSpaceFree() { NSError *error = nil ; 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; } /// String 's md5 hash. static NSString *_YYNSStringMD5(NSString *string ) { if (!string ) return nil ; NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding ]; unsigned char result [CC_MD5_DIGEST_LENGTH ]; CC_MD5 (data.bytes, (CC_LONG )data.length, result ); return [NSString stringWithFormat: @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" , result [0 ], result [1 ], result [2 ], result [3 ], result [4 ], result [5 ], result [6 ], result [7 ], result [8 ], result [9 ], result [10 ], result [11 ], result [12 ], result [13 ], result [14 ], result [15 ] ]; } 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 ]; }); } 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; } 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); }
_YYDiskSpaceFree()
提供剩余磁盘空间的查询。_YYNSStringMD5(NSString *string)
提供字符串转 md5。
_YYDiskCacheInitGlobal()
用于初始化一个强-弱的NSMapTable
,保存YYDiskCache
对象。_YYDiskCacheGetGlobal(NSString *path)
用于根据路径 path 获取对应的YYDiskCache
对象。_YYDiskCacheSetGlobal(YYDiskCache *cache)
用于根据路径 path 在NSMapTable
保存一个YYDiskCache
对象。
主要实现 YYDiskCache的功能比如移除过期的对象、移除超过数量限制的对象等,主要通过YYKVStorage实现。 作者把保存类型分为三种:
1 2 3 4 5 6 7 8 typedef NS_ENUM (NSUInteger , YYKVStorageType ) { /// file system. YYKVStorageTypeFile = 0, /// in sqlite. YYKVStorageTypeSQLite = 1, /// based on your choice. YYKVStorageTypeMixed = 2, };
作者指明了原因:Typically, write data to sqlite is faster than extern file, but reading performance is dependent on data size. In my test (on iPhone 6 64G), read data from extern file is faster than from sqlite when the data is larger than 20KB.
4.YYKVStorage YYKVStorage没有阅读。乍一看是许多琐碎的 SQL 操作和文件操作,没有纳入阅读计划。
三、小结 YYCache属于小家碧玉型的开源作品,代码量不是很多,但是很精美。LRU配合双向链表也是很经典的算法题。我觉得最好玩的是容器元素的异步释放逻辑,以前确实没见过这种写法,作者也给出了解释,很值得学习借鉴。