YYMemoryCache文件
在分析代碼之前,首先給大家介紹一下雙向鏈表,如下圖所示:
雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數據結點中都有兩個指針,分別指向直接后繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和后繼結點。如果還不明白雙向鏈表是什么可以看一下數據結構,相信你就能明白了。
_YYLinkedMapNode
這是一個鏈表節點類,先看代碼
@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
這幾行代碼里需要解讀的知識點很多,首先來說說這個類是干嘛的,他表示的是一個雙向鏈表的某一個節點。
其次@package對于大家可能有點陌生,我也是第一次在OC里見到,最早見到package是在java里看到的,他表示的是一個包。在這里@package是什么意思,它也是表示在包內都能使用,準確來說是在framework內可用。
插播一下,我們對比一下@package和@private
@interface TestClass : NSObject {
@private
NSString *mNamePrivate;
@package
NSString *mTextPackage;
}
如果你這樣寫
我們看到當我們分別調用 t->mTextPackage 和 t->mTextPackage ,發現前者是ok的,后者會報錯提示“instance variable 'mNamePrivate' is private”。@package 一般很少用(對于framework內部相當于protected 對于framework外部相當于private)。
我們再次回到剛才還沒說完的_YYLinkedMapNode
_YYLinkedMapNode* _prev 定義了一個指針指向前一個節點,還有一個_YYLinkedMapNode *_next指向后一個節點。我們注意到每一個節點都使用__unsafe_unretained ,這是為了防止循環強引用,因為這里的結構是雙向鏈表,很可能出現循環強引用,最后就都不會給釋放。
_YYLinkedMap
剛才說到_YYLinkedMapNode是一個節點,那么_YYLinkedMap就是鏈表,鏈表的作用是將所有節點串聯起來形成我們想要的雙向鏈表數據結構。
@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; // 是否在主線程上,異步釋放 _YYLinkedMapNode對象
BOOL _releaseAsynchronously; //是否異步釋放 _YYLinkedMapNode對象
}
鏈表的操作
insertNodeAtHead
- (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;
}
}
插入到頭部節點,首先使用字典retain住節點緩存對象,然后讓內存消耗自增,然后判斷如果存在頭部節點插入,注意鏈表操作的順序,最后一步才是_head = node ,否則head節點會丟失。
bringNodeToHead
- (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;
}
這個函數的功能是將某個節點從其他位置移到頭部,操作步驟如下圖所示,
removeNode
- (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;
}
先從字典移除節點,此時節點就沒有retain指向了,自減內存消耗,然后判斷刪除的節點是否是頭節點或尾節點,操作步驟類似bringNodeToHead第一個演示圖。
removeAll
- (void)removeAll {
_totalCost = 0;
_totalCount = 0;
_head = nil;
_tail = nil;
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); // hold and release in specified queue
});
} else if (_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
CFRelease(holder); // hold and release in specified queue
});
} else {
CFRelease(holder);
}
}
}
首先清空內存統計數據,然后清空頭結點和尾節點。因為全部的節點都是由字典強引用,所以獲取字典判斷是否還有節點。如果還存在未刪除的節點,就接著往下走。如果允許異步釋放,并設置的是在主線程釋放,那么就在主線程釋放;如果允許異步釋放,并設置不能在主線程釋放,則在YY的專用釋放線程釋放。如果允許主線程釋放,并且當前不在主線程則使用主線程釋放(避免死鎖)。都不滿足則使用同步釋放。
總結:
作者這么費勁的寫一個專門操作雙向鏈表的操作類到底是出于什么目的,這個其實就是計算機操作系統里的一個算法LRU(近期最少使用緩存項)淘汰算法,這里是用頭插法實現的,用這個算法對緩存數據進行淘汰控制,以便內存里的緩存數據命中率更高。因為畢竟內存大小有限,這也是YYMemoryCache精髓。
比較遺憾的YYCache是不支持存活時間的。不過這是可以理解的,作者覺得會和LRU 淘汰算法有沖突吧。
微博賬號:梅嘉慶(點擊關注)