這篇文章大多是我自己的基于iOS開(kāi)發(fā)一個(gè)想法過(guò)程,實(shí)現(xiàn)并不難涨颜。不過(guò)我并不會(huì)貼出全部代碼项滑,天知道我組織文章的根據(jù)是什么依沮,能看懂都是緣分。
前言
討論緩存時(shí)枪狂,都會(huì)分內(nèi)存緩存和硬盤(pán)緩存危喉,本文主要是基于內(nèi)存緩存的,但是完全可以稍加改動(dòng)便可以應(yīng)用于硬盤(pán)緩存中州疾。在做其他文件IO的時(shí)辜限,會(huì)有很多情況會(huì)基于C⊙媳停基于此列粪,我選擇了使用C來(lái)實(shí)現(xiàn)核心部分。而它的作用是主要用于WebView在內(nèi)存中的緩存數(shù)據(jù)谈飒。
關(guān)于LFU的一些概念之類(lèi)的東西就不說(shuō)了,原理實(shí)現(xiàn)網(wǎng)上一大堆态蒂『即耄基于自己?jiǎn)捂湵硗ㄟ^(guò)棧來(lái)實(shí)現(xiàn)LFU的方式實(shí)現(xiàn)一下,這不一定是最優(yōu)解钾恢,也有可能是最差解手素。不管怎么說(shuō)鸳址,先通過(guò)一個(gè)圖闡述一下主要的數(shù)據(jù)結(jié)構(gòu):
多想一點(diǎn)兒東西
在看了上圖之后,首先的想法就是去把具體算法擼出來(lái)泉懦。但是在開(kāi)動(dòng)寫(xiě)代碼之前稿黍,我們需要想的東西應(yīng)該要更多。在面向過(guò)程的思想中想開(kāi)來(lái)崩哩,使用棧的時(shí)候取出操作可以很快速的完成最快可以達(dá)到時(shí)間復(fù)雜度為O(1)巡球,而在維護(hù)棧順序的時(shí)候,最壞的時(shí)間復(fù)雜度為O(N)邓嘹,其查找和位置更新是在同一個(gè)循環(huán)中完成的酣栈,避免了不必要的時(shí)間消耗。
加入到面向?qū)ο笾?/h3>
顯然用C來(lái)做業(yè)務(wù)邏輯開(kāi)發(fā)始終有點(diǎn)兒蹩腳汹押,所以得引入到面向?qū)ο笾锌篌荨N野堰@個(gè)類(lèi)取名叫做PPWebFetchCache
吧,既然都面向?qū)ο罅伺锛郑辉倥c(diǎn)兒設(shè)計(jì)模式進(jìn)去窖维?考慮到易于操作性,對(duì)于PPWebFetchCache類(lèi)妙痹,我做了一個(gè)單例铸史,當(dāng)然也可以自己生成一個(gè)對(duì)象。到了這里下面應(yīng)該要想的就是對(duì)象的屬性和操作方法等等事情细诸,但是自己想著就用了一個(gè)單例模式這不是表明我設(shè)計(jì)模式很low嗎沛贪?不過(guò)事實(shí)的確是這樣,我沒(méi)有什么好的設(shè)計(jì)模式拿出來(lái)~~~
所以我就硬塞了一個(gè)簡(jiǎn)單工廠模式進(jìn)去震贵。它做什么呢利赋?我想的是“現(xiàn)在只是做LFU,萬(wàn)一哪一天變化來(lái)了猩系,讓我用寫(xiě)一個(gè)LRU的緩存策略媚送,那我不是死得很慘!”寇甸,所以我又創(chuàng)建了一個(gè)繼承于PPWebFetchCache類(lèi)的PPWebFetchLFUCache
塘偎,和另一個(gè)用于將來(lái)實(shí)現(xiàn)LRU算法的PPWebFetchLRUCache
類(lèi)。并在最后給父類(lèi)添加了屬性maxMemoryCost拿霉、maxMemoryCountLimit和兩個(gè)操作方法storeCacheInMemory吟秩、memoryCacheWithURL。
走進(jìn)實(shí)現(xiàn)細(xì)節(jié)
上面說(shuō)的LFU是針對(duì)于某一個(gè)URL的使用次數(shù)绽淘。在思考如何使用最少時(shí)間拿到URL對(duì)應(yīng)的數(shù)據(jù)時(shí)涵防,顯然散列表最理想的數(shù)據(jù)結(jié)構(gòu),而且在iOS中用散列表實(shí)現(xiàn)的代表便是Dictionary
沪铭。所以大致的邏輯是:
使用NSDictionary來(lái)實(shí)現(xiàn)URL和數(shù)據(jù)一對(duì)一在內(nèi)存中的存儲(chǔ)壮池。而LFU主要用于管理某一URL的使用次數(shù)偏瓤,淘汰掉使用次數(shù)最少的URL,并在內(nèi)存字典中刪除對(duì)應(yīng)的數(shù)據(jù)椰憋。
PPWebFetchCache對(duì)外暴露的接口只有存和取厅克,而具體的加入和刪除操作則是在內(nèi)部通過(guò)maxMemoryCost、maxMemoryCountLimit控制實(shí)現(xiàn)橙依。
異步環(huán)境
顯然在數(shù)據(jù)存取的時(shí)候证舟,我們不應(yīng)該放在主線程中去做這些事兒。因此我創(chuàng)建串行隊(duì)列用來(lái)執(zhí)行這些事務(wù):
static dispatch_queue_t background_serialQueue(){
static dispatch_queue_t queue;
if (!queue) {
char *const label = "com.example.webprefetchCache";
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
queue = dispatch_queue_create(label, attr);
}
return queue;
}
void async_inbackground(void(^block)(void)){
dispatch_async(background_serialQueue(), block);
}
為什么要用串行隊(duì)列票编?因?yàn)樵陉?duì)列內(nèi)部操作褪储,我不需要關(guān)心會(huì)出現(xiàn)資源競(jìng)爭(zhēng)的問(wèn)題。而在串行隊(duì)列以外其他隊(duì)列中來(lái)操作單例的相關(guān)數(shù)據(jù)時(shí)慧域,我就需要去關(guān)心的線程安全的問(wèn)題鲤竹。因此我直接使用適用于靜態(tài)分配的的互斥量PTHREAD_MUTEX_INITIALIZER來(lái)保證數(shù)據(jù)的同步操作。反觀如果我去使用pthread_mutex_init
來(lái)動(dòng)態(tài)生成一個(gè)互斥量的話昔榴,我還要操心什么時(shí)候去destroy掉它(當(dāng)然這里得仔細(xì)思考造成死鎖的問(wèn)題)辛藻。
static inline pthread_mutex_t mutext_lock(void){
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
return lock;
}
static void safeProgressWith(void(^block)(void)){
pthread_mutex_t lock = mutext_lock();
pthread_mutex_lock(&lock);
block();
pthread_mutex_unlock(&lock);
}
在加鎖等操作時(shí),盡量讓其顆粒度更低互订。這樣可以減少不必要的線程處于waiting狀態(tài)吱肌,也就相應(yīng)地減少出現(xiàn)低優(yōu)先級(jí)線程餓死的情況發(fā)生(盡量減少CPU密集型線程的時(shí)間片)。
LFU的具體實(shí)現(xiàn)
LFU只是針對(duì)于URL的淘汰策略仰禽,淘汰了URL之后氮墨,根據(jù)該URL到NSDictionary中找出對(duì)應(yīng)的數(shù)據(jù)進(jìn)行移除。這里使用鏈表的方式實(shí)現(xiàn)棧結(jié)構(gòu)吐葵,其結(jié)構(gòu)如下:
typedef struct __PPWebLFUFetchInlayerNode_ * _PPWebLFUInlayerNodeRef;
typedef struct __PPWebLFUFetchInlayerNode_ {
char *url;
int use_count;
_PPWebLFUInlayerNodeRef next;
}_PPWebLFUFetchInlayerNode;
在PPWebFetchLFUCache類(lèi)中保存了一個(gè)_PPWebLFUInlayerNodeRef的指針规揪,這個(gè)指針指向棧頂:
@interface PPWebFetchLFUCache:PPWebFetchCache{
_PPWebLFUInlayerNodeRef _lfuStack;
}
走到這里我們大可以直接使用_lfuStack成員變量來(lái)對(duì)棧進(jìn)行相應(yīng)的操作,但是我們可以更進(jìn)一步温峭!這里思維跳躍一下猛铅,當(dāng)我們?cè)诓迦胍粋€(gè)節(jié)點(diǎn)時(shí)如何去判斷當(dāng)前節(jié)點(diǎn)是新增節(jié)點(diǎn)、還是存在于棧中的節(jié)點(diǎn)凤藏、抑或是需要?jiǎng)h除的結(jié)點(diǎn)奸忽?
如果只是簡(jiǎn)單的回答“我在插入節(jié)點(diǎn)時(shí),先遍歷一遍椧咀看元素是否存在于其中”栗菜,這樣做毫無(wú)意義,而且平添一倍的時(shí)間消耗蹄梢,因?yàn)楹竺娴牟迦氩僮鲿r(shí)疙筹,還要去遍歷一次找到對(duì)應(yīng)的節(jié)點(diǎn)位置。
為了能夠更好地在同一個(gè)循環(huán)中處理插入數(shù)據(jù),查找數(shù)據(jù)腌歉,刪除數(shù)據(jù)等操作。這里需要在執(zhí)行C操作時(shí)在適當(dāng)?shù)攸c(diǎn)給我們回調(diào)齐苛,讓我們有機(jī)會(huì)在一次循環(huán)中做完這些操作翘盖。為什么要用回調(diào)呢?我們完全可以把刪除的相關(guān)邏輯放在某一次循環(huán)中凹蜂,這樣就需要我們?cè)谡{(diào)用邏輯時(shí)傳入一些判斷條件馍驯。這無(wú)疑是增加了算法的局限性,從另一點(diǎn)來(lái)說(shuō)玛痊,這個(gè)算法的適用范圍就大大降低了汰瘫。
所以我引入了一個(gè)上下文環(huán)境,這個(gè)環(huán)境主要用于包裹相關(guān)信息數(shù)據(jù)和函數(shù)指針回調(diào):
typedef struct __PPWebLFUFetchCacheContext *PPWebLFUFetchCacheContextRef;
typedef struct __PPWebLFUFetchCacheContext {
_PPWebLFUInlayerNodeRef *node;/// root
void *info;/// 一般傳入fetchCacher對(duì)象
void (*appendInStackRootCallback)(void *info, char *const key);/// 棧頂插入回調(diào)
void (*appendInStackBottomCallback)(void *info, char *const key);/// 棧底插入回調(diào)
void (*appendInStackCallback)(void *info, char *const key);/// 棧中插入回調(diào)
}PPWebLFUFetchCacheContext;
/// 調(diào)用這個(gè)方法之后擂煞,如果不再需要使用這個(gè)指針混弥,需要調(diào)用free來(lái)釋放內(nèi)存空間
PPWebLFUFetchCacheContextRef PPWebLFUFetchCacheContextCreate(void *root,
void *info,
void (* _Nonnull appendInStackRootCallback)(void *info, char *const key),
void (* _Nonnull appendInStackBottomCallback)(void *info, char *const key),
void (* _Nonnull appendInStackCallback)(void *info, char *const key)){
PPWebLFUFetchCacheContextRef ctx = (PPWebLFUFetchCacheContextRef)malloc(sizeof(PPWebLFUFetchCacheContext));
ctx->node = root;
ctx->info = info;
ctx->appendInStackRootCallback = appendInStackRootCallback;
ctx->appendInStackBottomCallback = appendInStackBottomCallback;
ctx->appendInStackCallback = appendInStackCallback;
return ctx;
}
至于這里為什么選擇一個(gè)上下文?當(dāng)我們需要多個(gè)回調(diào)時(shí)对省,完全沒(méi)有必要把每一個(gè)回調(diào)都添加到函數(shù)參數(shù)中去蝗拿,我們可以把這些參數(shù)包裝起來(lái)。而且這樣包裝起來(lái)做還有一個(gè)優(yōu)勢(shì)蒿涎,就是新增回調(diào)場(chǎng)景時(shí)就要方便許多哀托!
元素添加
現(xiàn)在所有的條件都已具備,是時(shí)候來(lái)處理這些具體的邏輯了劳秋。就像是在學(xué)紅黑樹(shù)的時(shí)候一般都會(huì)把它那5個(gè)特性先提出來(lái)是吧仓手。所以這里需要明確幾點(diǎn)特性:
- 1、由鏈表實(shí)現(xiàn)的一個(gè)棧玻淑,只有一個(gè)根節(jié)點(diǎn)(上面提到的嗽冒,包裝在上下文中的lfustack);
- 2岁忘、棧的深度是有限制的辛慰;
- 3、添加和刪除操作是基于棧頂干像;
- 4帅腌、棧內(nèi)元素的使用次數(shù)是從小到大,從上到下生長(zhǎng)麻汰;
基于以上速客,我定義了一個(gè)元素添加函數(shù)的原型:
bool _PPWebFetchLFUInlayerQueueAdd(PPWebLFUFetchCacheContextRef context,char *const url)
很明顯傳入的context是需要在外面創(chuàng)建好的一個(gè)指針變量,但是context的具體成員變量我們沒(méi)有控制五鲫,全部傳入NULL都可以(因?yàn)閼心缰埃幌雽?duì)函數(shù)指針做非空判斷,所以我把函數(shù)指針設(shè)置為_(kāi)Nonnull。浪耘。乱灵。),這沒(méi)有什么問(wèn)題七冲。因此首先要做的就是判斷棧是否為空:
if (!(*(context->node))) {/// 創(chuàng)建棧頂指針
_PPWebLFUInlayerNodeRef node = allocNewNode(url);
if (!node) {
return false;
}
__block _PPWebLFUInlayerNodeRef *_broot = (context->node);
safeProgressWith(^{
*_broot = node;
(context->appendInStackRootCallback)(context->info,url);
});
return true;
}
在上面這段代碼中我們使用了context的一個(gè)函數(shù)指針回調(diào)痛倚,當(dāng)在空棧中加入根節(jié)點(diǎn),這是符合該函數(shù)指針回調(diào)語(yǔ)義的澜躺。
此時(shí)的棧分布情況很簡(jiǎn)單蝉稳,但還是畫(huà)出來(lái)更加明顯:
在這之后插入結(jié)點(diǎn)時(shí),我們便需要考慮新添加進(jìn)來(lái)的URL是新節(jié)點(diǎn)還是在原有節(jié)點(diǎn)上增加使用次數(shù)掘鄙。這里我們需要一個(gè)循環(huán)從根節(jié)點(diǎn)開(kāi)始遍歷棧耘戚,如果找到了對(duì)應(yīng)的URL,便將其使用次數(shù)加一操漠,如果走到棧底還是未能命中對(duì)應(yīng)的URL收津,則需要以該URL為數(shù)據(jù)創(chuàng)建一個(gè)新節(jié)點(diǎn),并將這個(gè)節(jié)點(diǎn)作為棧根颅夺。實(shí)現(xiàn)代碼如下:
if (0 == strcmp(lead->url, url)) {
(context->node)->use_count++;
}else{
(context->appendInStackRootCallback)(context->info,url);
return appendRootNodeInStack((context->node), url);
}
上面涉及到的函數(shù)appendRootNodeInStack主要用于生成一個(gè)節(jié)點(diǎn)之后朋截,并將該節(jié)點(diǎn)設(shè)置為根:
bool appendRootNodeInStack(_PPWebLFUInlayerNodeRef *root ,char *const url){
/// 在棧頂插入
_PPWebLFUInlayerNodeRef node = allocNewNode(url);
if (!node) {
return false;
}
__block _PPWebLFUInlayerNodeRef *_broot = root;
safeProgressWith(^{
node->next = *_broot;
*_broot = node;
});
return true;
}
所以現(xiàn)在會(huì)出現(xiàn)兩種情況,如下所示:
這段邏輯代碼目前并沒(méi)有放在循環(huán)中來(lái)做吧黄,它和棧中已經(jīng)存在四個(gè)部服、五個(gè)節(jié)點(diǎn)的情況是類(lèi)似的,但它的情況要簡(jiǎn)單許多拗慨,它只需要處理使用次數(shù)更新或者頭節(jié)點(diǎn)插入的情況廓八,不會(huì)涉及到刪除(除非你不做緩存)、移位操作赵抢。到最后我會(huì)把這段代碼合并起來(lái)剧蹂,而那正是我設(shè)計(jì)這套算法的中心思想。
節(jié)點(diǎn)移動(dòng)和刪除
針對(duì)節(jié)點(diǎn)的移動(dòng)烦却,需要考慮多個(gè)情況宠叼,包括:從棧頂移動(dòng)到棧底、從棧頂移動(dòng)到棧中某一個(gè)位置其爵、從棧中某一個(gè)位置移動(dòng)到棧中另一個(gè)位置冒冬、從棧中某一個(gè)位置移動(dòng)到棧底、不移動(dòng)摩渺。
我把這些情況依次描述到圖中简烤,這樣看著更直觀:
下面是后面兩種情況:
從上面虛線箭頭到實(shí)線箭頭的變化可以很明顯看出來(lái),是復(fù)雜了不少摇幻。而最后一種無(wú)變化的横侦,我就沒(méi)有列出來(lái)挥萌。看看前面四種情況變化后的棧元素排列情況枉侧,從左到右引瀑,從上到下依次是:
- 2->3->4->1;
- 3->2->4->1;
- 2->4->3->1;
- 2->3->1->4;
從上面四種情況來(lái)看,對(duì)于一個(gè)節(jié)點(diǎn)的移動(dòng)可以分為兩部拆開(kāi)來(lái)看榨馁,分別是——取出伤疙、放入兩個(gè)過(guò)程。我直接把中間的的算法列出來(lái):
_PPWebLFUInlayerNodeRef previous = lead;
_PPWebLFUInlayerNodeRef pivot = NULL;
_PPWebLFUInlayerNodeRef prepivot = NULL;
do {
if (0 != strcmp(lead->url, url)) {
if (!pivot) {
continue;
}
if (pivot->use_count <= lead->use_count) {
break;/// 跳出循環(huán)去執(zhí)行放入
}
if (*(context->node)==pivot) {
__block _PPWebLFUInlayerNodeRef *_broot = (context->node);
safeProgressWith(^{
*_broot = previous->next;
});
}else{/// 取出
prepivot->next=pivot->next;
}
continue;
}
lead->use_count++;
pivot = lead;
prepivot = previous;
} while ((void)(previous=lead),(void)(lead=lead->next),lead);
if (!pivot) {/// 在棧頂插入
(context->appendInStackRootCallback)(context->info,url);
return appendRootNodeInStack((context->node), url);
}
if (!lead) {/// 處理?xiàng)5浊闆r
previous->next=pivot;
pivot->next=NULL;
(context->appendInStackBottomCallback)(context->info,url);
}else{/// 處理?xiàng)V械姆湃? pivot->next=previous->next;
previous->next=previous==pivot?lead:pivot;
(context->appendInStackCallback)(context->info,url);
}
這段代碼把上面提到的if-else判斷也一起合并進(jìn)來(lái)了辆影,這里pivot主要是用來(lái)記錄找到目標(biāo)URL的哨兵,而prepivot用來(lái)記錄哨兵前面一個(gè)節(jié)點(diǎn)(如果使用雙向鏈表完全可以不用這個(gè)零時(shí)變量)黍特。
到這里基本上是把該算法的核心部分說(shuō)完了蛙讥,該算法的最壞時(shí)間復(fù)雜度就是O(N),這種最壞時(shí)間復(fù)雜度的情況分別是:新節(jié)點(diǎn)插入灭衷,棧頂一次直接移動(dòng)棧底(這個(gè)情況是發(fā)生在使用次數(shù)都為1時(shí)次慢,棧頂元素此時(shí)+1的情況)。最優(yōu)的時(shí)間復(fù)雜度情況是O(1)翔曲,直接棧頂數(shù)據(jù)更新迫像。
最后就是節(jié)點(diǎn)的刪除操作,僅僅只是刪除操作時(shí)間復(fù)雜度肯定是O(1)的瞳遍。但是事情往往沒(méi)有這么簡(jiǎn)單闻妓,必須要考慮當(dāng)前添加進(jìn)來(lái)的元素是到達(dá)容量限制的新元素,還是棧里面已經(jīng)存在的元素呢掠械?難道我們又要去遍歷一次棧然后來(lái)做刪除操作嗎由缆?這是完全沒(méi)有必要的,因?yàn)橐霈F(xiàn)刪除節(jié)點(diǎn)的情況猾蒂,肯定是發(fā)生在向棧中Push元素時(shí)發(fā)生均唉。
因此我將上面各個(gè)情況分為三種大體情況,并為這三種情況提供了三個(gè)回調(diào)肚菠,而這個(gè)三個(gè)回調(diào)都是放在上面的context中:
- 在棧頂插入元素(appendInStackRootCallback)舔箭;
- 處理?xiàng)5浊闆r(appendInStackBottomCallback);
- 處理中間節(jié)點(diǎn)(appendInStackCallback)蚊逢;
基于這:
我們可以一次循環(huán)中完成新增层扶、移動(dòng)、刪除操作时捌!
上面提到的三個(gè)回調(diào)怒医,可以通過(guò)調(diào)用PPWebFetchLFUCache實(shí)例方法來(lái)看一下一個(gè)整體過(guò)程:
- (BOOL)insertCacheMap:(NSData *)object forKey:(NSString *)key{
PPWebLFUFetchCacheContextRef ctx = PPWebLFUFetchCacheContextCreate(
&_lfuStack,
(__bridge void *)(self),
&progressingAppendInStackRootCallback,
&progressingAppendInStackBottomCallback,
&progressingAppendInStackCallback);
bool result = _PPWebFetchLFUInlayerQueueAdd(ctx, (char *)[key UTF8String]);
if (result == false) {
PPWebLFUFetchCacheContextRelease(&ctx);
return NO;
}
if (![self.cacheMap.allKeys containsObject:key]) {
safeProgressWith(^{
[self.cacheMap setObject:object forKey:key];
self.currentCacheUsage += object.length;
});
}
PPWebLFUFetchCacheContextRelease(&ctx);
return YES;
}
在上面創(chuàng)建上下文的代碼中,第一個(gè)參數(shù)為保存在PPWebFetchLFUCache單例中的一個(gè)成員變量奢讨,而info參數(shù)主要用來(lái)傳遞self稚叹,這里用context時(shí)焰薄,_lfuStack會(huì)被context保留,而_lfuStack又會(huì)被PPWebFetchLFUCache單例保留扒袖,但是在函數(shù)返回之前會(huì)對(duì)context做release操作塞茅,會(huì)把對(duì)_lfuStack的保留置空,所以不要想著OC里面常出現(xiàn)的引用計(jì)數(shù)不會(huì)降為0的問(wèn)題季率。而且也不會(huì)出現(xiàn)相互持有的關(guān)系野瘦。
而回調(diào)函數(shù)中,主要來(lái)看progressingAppendInStackRootCallback的回調(diào):
void progressingAppendInStackRootCallback(void *info, char *const key){
PPWebFetchLFUCache *cacher = (__bridge PPWebFetchLFUCache *)(info);
if (!cacher) {
return;
}
if (cacher.cacheMap.allKeys.count >= kMaxMemoryCountLimit) {
[cacher deleteCacheMap];
progressingAppendInStackRootCallback(info, key);
}else{
return;
}
}
下面我直接把刪除函數(shù)貼出來(lái)飒泻,這并沒(méi)有什么難點(diǎn):
_PPWebLFUInlayerNodeRef _PPWebFetchLFUInlayerQueueDelete(PPWebLFUFetchCacheContextRef context ,char **url){
if (!(context->node)) {
return NULL;
}
_PPWebLFUInlayerNodeRef lead = NULL;
lead = *(context->node);
__block _PPWebLFUInlayerNodeRef *_broot = (context->node);
if ((*_broot)->next) {
safeProgressWith(^{
*_broot = (*_broot)->next;
});
}
if (url) {
(*url) = (lead->url);
}
return lead;
}
上面出現(xiàn)的deleteCacheMap方法中會(huì)把不再使用的節(jié)點(diǎn)free掉鞭光。
后語(yǔ)
上面代碼中很多地方都用到了safeProgressWith函數(shù),其實(shí)現(xiàn)也在上面列出來(lái)了泞遗。使用它的目的有兩個(gè):
第一個(gè)惰许、PPWebFetchLFUCache類(lèi)的操作是可以在多線程異步環(huán)境下操作的,所以我必須要保證cacheMap的數(shù)據(jù)同步史辙;
第二個(gè)汹买、雖然基于C的操作我都是放在一個(gè)串行的隊(duì)列中進(jìn)行:
async_inbackground(^{
BOOL result = [self insertCacheMap:data forKey:url];
if (complete) {
complete(result);
}
});
但是_lfuStack成員變量是可以通過(guò)hook的方法拿到,并讓其在異步環(huán)境下去進(jìn)行修改聊倔,這個(gè)我沒(méi)法去控制晦毙,但是我要做到在LFU內(nèi)做到一個(gè)同步操作,所以基于跟節(jié)點(diǎn)的操作都是在加鎖狀態(tài)下完成的耙蔑。這里需要注意的就是不要出現(xiàn)互斥鎖的嵌套使用见妒,如果使用的是同一個(gè)鎖變量的話,那肯定會(huì)造成死鎖的甸陌。
完徐鹤。。邀层。