YYCache 閱讀總結(jié)

YYCache 的基本使用

- (void)synchronizingStorageForCache {
    
    NSString *value1 = @"朽木自雕";
    NSString *key1 = @"key1";
    
    NSArray *value2 = @[@"1", @"2", @"3", @"4", @"5"];
    NSString *key2 = @"key2";
    
    // 創(chuàng)建緩存對象
    YYCache *cache = [[YYCache alloc]initWithName:@"cacheTest"];
    [cache setObject:value1 forKey:key1];
    [cache setObject:value2 forKey:key2];
    
    // 判斷對象是否存在
    if ([cache containsObjectForKey:key1]) {
        // 取出緩存
        id value = [cache objectForKey:key1];
        NSLog(@"%@", value);
    }
    
    // 判斷對象是否存在
    if ([cache containsObjectForKey:key2]) {
        // 取出緩存
        id value = [cache objectForKey:key2];
        NSLog(@"%@", value);
    }
}

其他的 API 的使用很簡單蘸秘,不在這里逼逼叨逼逼叨的

類圖

YYCache 類圖

類說明

YYCache

YYCache 是提供用用戶使用的對象恨搓,內(nèi)部對 YYMemoryCache 和 YYDiskCache 功能的整合封裝。為 YYMemoryCache 提供了多線程功能好爬,而 YYDiskCache 對象本身內(nèi)部封裝了異步讀寫功能局雄。封裝的功能包括:

初始化緩存對象
- (nullable instancetype)initWithName:(NSString *)name;
- (nullable instancetype)initWithPath:(NSString *)path;
+ (nullable instancetype)cacheWithName:(NSString *)name;
+ (nullable instancetype)cacheWithPath:(NSString *)path;

這幾個方法功能是一直的,最終方法都是會掉“initWithPath:”

判斷是否存在某條緩存
- (BOOL)containsObjectForKey:(NSString *)key;
- (void)containsObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, BOOL contains))block;
  1. 方法一 實(shí)現(xiàn):先去內(nèi)存緩存中查找存炮,如果沒有查找就去磁盤緩存中找炬搭,找到就返回 YES
  2. 方法二 實(shí)現(xiàn):同步在在內(nèi)存中查找緩存,如果找到了穆桂,使用異步返回宫盔,如果沒有找到,就調(diào)用磁盤緩存的異步查找 API享完,并把查找結(jié)果異步回調(diào)
通過 key 取出緩存
- (nullable id<NSCoding>)objectForKey:(NSString *)key;
- (void)objectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, id<NSCoding> object))block;
  1. 方法一 實(shí)現(xiàn):先去內(nèi)存緩存中查找灼芭,如果沒有查找就去磁盤緩存中找,找到就返回結(jié)果般又。如果是在磁盤緩存中找到的緩存彼绷,先把緩存存入內(nèi)存緩存中,然后再返回結(jié)果
  2. 方法二 實(shí)現(xiàn):同步在在內(nèi)存中查找緩存茴迁,如果找到了寄悯,使用異步返回,如果沒有找到堕义,就調(diào)用磁盤緩存的異步查找 API猜旬,如果是在磁盤緩存中找到的緩存,先把緩存存入內(nèi)存緩存中倦卖,然后再返回結(jié)果
增洒擦、改--緩存對象
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(nullable void(^)(void))block;
  1. 方法一 實(shí)現(xiàn)邏輯:首先調(diào)用內(nèi)存緩存設(shè)置緩存API(setObject:forKey:),然后調(diào)用磁盤緩存設(shè)置API(setObject:forKey:)
  2. 方法二 實(shí)現(xiàn)邏輯:首先調(diào)用內(nèi)存緩存設(shè)置緩存API(setObject:forKey:)怕膛,然后調(diào)用磁盤緩存異步設(shè)置API(setObject:forKey:)
刪除 key 對應(yīng)的緩存記錄
- (void)removeObjectForKey:(NSString *)key;
- (void)removeObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key))block;
  1. 方法一 實(shí)現(xiàn)邏輯:首先調(diào)用內(nèi)存緩存刪除 API(removeObjectForKey:)熟嫩,然后調(diào)用磁盤緩存刪除API(removeObjectForKey:)
  2. 方法二 實(shí)現(xiàn)邏輯:首先調(diào)用內(nèi)存緩存刪除 API(removeObjectForKey:),然后調(diào)用磁盤緩存異步刪除API(removeObjectForKey:withBlock:)
清空所有緩存記錄
- (void)removeAllObjects;
- (void)removeAllObjectsWithBlock:(void(^)(void))block;
- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
                                 endBlock:(nullable void(^)(BOOL error))end;
  1. 方法一 實(shí)現(xiàn):首先調(diào)用內(nèi)存緩存清空 API(removeAllObjects)褐捻,然后調(diào)用磁盤緩存清空 API(removeAllObjects)
  2. 方法二 實(shí)現(xiàn):首先調(diào)用內(nèi)存緩存清空 API(removeAllObjects)邦危,然后調(diào)用磁盤緩存異步清空 API(removeAllObjects)
  3. 方法三 實(shí)現(xiàn):首先調(diào)用內(nèi)存緩存清空 API(removeAllObjects)洋侨,然后調(diào)用磁盤緩存帶刪除進(jìn)度的清空緩存 API (removeAllObjectsWithProgressBlock:endBlock:)

YYMemoryCache

YYMemoryCache 內(nèi)部有一個儲存對象舍扰,實(shí)現(xiàn)分為兩部分:

  1. 第一部分倦蚪,淘汰算法,這里使用一個雙向鏈表边苹,每個節(jié)點(diǎn)為 _YYLinkedMapNode 類對象陵且,通過訪問最后訪問時間來對鏈表進(jìn)行排列,最新訪問的緩存節(jié)點(diǎn)放在鏈表的頭部个束,淘汰算法只需要將鏈表未尾節(jié)點(diǎn)移除即可
  2. 第二部分慕购,查找算法,這里使用的是 CFMutableDictionaryRef 散列表進(jìn)行存儲

YYMemoryCache 的多線程安全是 使用 pthread_mutex_t(互斥鎖) 來完成

YYDiskCache

  1. YYDiskCache 是對 YYKVStorage 封裝了異步訪問 API茬底,多線程安全使用 dispatch_semaphore_t(二元信號量) 來完成
  2. YYDiskCache 中同一功能方法沪悲,同步和異步的區(qū)別:異步方法的實(shí)現(xiàn)其實(shí)質(zhì)上就是只是異步的調(diào)用了同步方法,所以我在下面方法的介紹的時候只介紹同步方法的實(shí)現(xiàn)
  3. YYDiskCache 自動清理緩存機(jī)制阱表,內(nèi)部有一個這樣的 方法“_trimRecursively” 在類對象實(shí)例化的時候會被調(diào)用殿如,仔細(xì)看一下源碼的實(shí)現(xiàn):
// 自動檢查緩存
- (void)_trimRecursively {
    __weak typeof(self) _self = self;
    // 延遲執(zhí)行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        // 清理緩存
        [self _trimInBackground];
        // 遞歸的調(diào)用
        [self _trimRecursively];
    });
}

// 在后臺線程中清理磁盤緩存
- (void)_trimInBackground {
    __weak typeof(self) _self = self;
    dispatch_async(_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        // 沒什么好解釋,磁盤數(shù)據(jù)的讀寫必須加鎖最爬,保證多線程讀寫安全
        Lock();
        [self _trimToCost:self.costLimit];
        [self _trimToCount:self.countLimit];
        [self _trimToAge:self.ageLimit];
        [self _trimToFreeDiskSpace:self.freeDiskSpaceLimit];
        Unlock();
    });
}

注釋都寫好了涉馁,不用再解釋吧。

YYDiskCache 判斷是否存在某條緩存
- (BOOL)containsObjectForKey:(NSString *)key {
    // 判斷參數(shù)的合法性
    if (!key) return NO;
    //上鎖爱致,沒什么好說的烤送,為了安全起見
    Lock();
    // 從數(shù)據(jù)庫中查詢
    BOOL contains = [_kv itemExistsForKey:key];
    Unlock();
    //返回結(jié)果
    return contains;
}

同時還存在一個功能相同,但是異步的方法(containsObjectForKey:withBlock:)糠悯,這個異步方法的實(shí)現(xiàn)就是異步調(diào)用了方法(containsObjectForKey:)帮坚,不妨看看源碼

- (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block {
    // 判斷參數(shù)的合法性
    if (!block) return;
    __weak typeof(self) _self = self;
    dispatch_async(_queue, ^{
        __strong typeof(_self) self = _self;
        BOOL contains = [self containsObjectForKey:key];
        block(key, contains);
    });
}
YYDiskCache 獲取某條緩存(objectForKey:)

這個方法的返回的是一個 遵守 NSCoding 協(xié)議的類對象,對象的解碼可以 customUnarchiveBlock 屬性互艾,自定義解碼试和。默認(rèn)內(nèi)部使用 NSCoding 歸檔工具解碼。

- (id<NSCoding>)objectForKey:(NSString *)key {
    // 判斷參數(shù)的合法性
    if (!key) return nil;
    //上鎖忘朝,沒什么好說的灰署,為了安全起見
    Lock();
    YYKVStorageItem *item = [_kv getItemForKey:key];
    Unlock();
    if (!item.value) return nil;
    
    id object = nil;
    if (_customUnarchiveBlock) {
        // 外部解壓
        object = _customUnarchiveBlock(item.value);
        // 自己解壓
    } else {
        @try {
            object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value];
        }
        @catch (NSException *exception) {
            // nothing to do...
        }
    }
    // 擴(kuò)展參數(shù)。為了做到速度的極致局嘁,這里也是拼了
    if (object && item.extendedData) {
        [YYDiskCache setExtendedData:item.extendedData toObject:object];
    }
    // 返回結(jié)果
    return object;
}
YYDiskCache 寫入緩存

同步寫入方法的實(shí)現(xiàn)(setObject: forKey:)

- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
    // 判斷 key 的合法性
    if (!key) return;
    // 當(dāng) object 為 nil 時溉箕,改存入為刪除
    if (!object) {
        [self removeObjectForKey:key];
        return;
    }
    // 獲取到擴(kuò)展數(shù)據(jù)
    NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];
    NSData *value = nil;
    // 外部壓縮
    if (_customArchiveBlock) {
        value = _customArchiveBlock(object);
    } else {
        @try {
            // 內(nèi)部壓縮
            value = [NSKeyedArchiver archivedDataWithRootObject:object];
        }
        @catch (NSException *exception) {
            // nothing to do...
        }
    }
    // 如果壓縮的值為空,就不用存數(shù)據(jù)了
    if (!value) return;
    NSString *filename = nil;
    // 判斷緩存存儲方式悦昵,如果不是 數(shù)據(jù)庫存儲肴茄,則進(jìn)入條件
    if (_kv.type != YYKVStorageTypeSQLite) {
        // 如果值的長度大于臨界值,則以文件的形式進(jìn)行存儲
        if (value.length > _inlineThreshold) {
            // 獲取文件名
            filename = [self _filenameForKey:key];
        }
    }
    // 數(shù)據(jù)寫入本地磁盤
    // 上鎖但指,沒什么好說的寡痰,為了安全起見
    Lock();
    [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
    Unlock();
}

對應(yīng)的異步寫入方法(setObject: forKey: withBlock:)抗楔,實(shí)現(xiàn)的套路異步調(diào)用同步寫入方法(setObject: forKey:)

YYDiskCache 刪除記錄的邏輯
  1. 刪除一條記錄(removeObjectForKey:),內(nèi)部實(shí)現(xiàn)是調(diào)用 YYKVStorage 的 removeObjectForKey:方法拦坠,并在調(diào)用的前后加上了鎖连躏。而對應(yīng)的異步方法(removeObjectForKey: withBlock:),使用異步線程調(diào)用了同步方法(removeObjectForKey:)
  2. 刪除所有記錄
    1. 不帶進(jìn)度刪除所有記錄(removeAllObjects)贞滨,內(nèi)部實(shí)現(xiàn)是調(diào)用 YYKVStorage 的 removeAllItems方法入热,并在調(diào)用的前后加上了鎖。而對應(yīng)的異步方法(removeAllObjectsWithBlock:)晓铆,實(shí)質(zhì)上使用異步線程調(diào)用了同步方法(removeAllObjects)
    2. 帶刪除進(jìn)度地刪除所有記錄(removeAllObjectsWithProgressBlock:endBlock:)勺良,內(nèi)部實(shí)現(xiàn)是異步調(diào)用 YYKVStorage 的 removeAllItemsWithProgressBlock:endBlock: 方法,并在這個方法的調(diào)用前后加了鎖骄噪。
YYDiskCache 磁盤清理邏輯
- (void)trimToCount:(NSUInteger)count;
- (void)trimToCost:(NSUInteger)cost;
- (void)trimToAge:(NSTimeInterval)age;

在幾種磁盤清理實(shí)現(xiàn)方式基本一致尚困,都是調(diào)用 YYKVStorage 對應(yīng)的清理方法

YYKVStorage

YYKVStorage 為磁盤緩存的核心類,提供給了外部數(shù)據(jù)庫(sqlite3)存儲以及系統(tǒng)文件(系統(tǒng)文件管理類 NSFileManager)存儲方式链蕊,在使用文件存儲時是配合數(shù)據(jù)庫存儲的事甜,文件的描述信息存在數(shù)據(jù)庫中。

  • 磁盤的淘汰算法實(shí)現(xiàn)示弓,淘汰算法使用的是最后訪問時間來進(jìn)行淘汰的讳侨,每當(dāng)數(shù)據(jù)庫中某條記錄被訪問到后,這條記錄就會更新最后訪問時間為當(dāng)前時間奏属,而在刪除過期過期緩存時跨跨,只需要根據(jù)確定過期時間即可。
  • 磁盤的查找算法實(shí)現(xiàn)囱皿,前面說過勇婴,磁盤存儲分為兩種,一種是數(shù)據(jù)庫嘱腥,每條緩存對應(yīng)數(shù)據(jù)庫表中的一條記錄耕渴,另一種是系統(tǒng)文件,每條緩存對應(yīng)系統(tǒng)文件中的一個文件齿兔。在通過 key 查找某個緩存時橱脸,首先去數(shù)據(jù)庫中找到 key 對應(yīng)的記錄,把去出來的數(shù)據(jù)轉(zhuǎn)換成 YYKVStorageItem 類對象分苇,判斷 filename 字段是否為空添诉,如果為空,說明數(shù)據(jù)存在數(shù)據(jù)空医寿,則直接使用這個對象的 value 字段栏赴,如果不為空,則說明緩存實(shí)體數(shù)據(jù)存在系統(tǒng)文件中靖秩,通過 filename 去系統(tǒng)文件中找到對應(yīng)文件须眷,并賦值給這個對象的 value 字段竖瘾。

總結(jié)

閱讀 YYCache 源碼,從開始到讀完一共花了三天的樣子花颗,真心感嘆 大神寫得代碼 跟我這個凡人的差距捕传。在閱讀源碼的過程中有很多只是點(diǎn)是自己沒有了解過的,得去問度娘查資料捎稚,所以閱讀得比較慢乐横,比如“pthread_mutex_t 特性”、“sqlite3 特性”今野、“sql 語句”、“數(shù)據(jù)庫的 wal 模式”罐农、“數(shù)據(jù)庫 synchronous 的模式選擇與區(qū)別”条霜、“CFMutableDictionaryRef”等等。在源碼很多的實(shí)現(xiàn)簡直不要太精妙涵亏,希望下次能抽出時間去看看 YYText 的源碼宰睡,如果我能有這兩把刷子,此生足以????气筋。

源碼筆記

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拆内,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宠默,更是在濱河造成了極大的恐慌麸恍,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搀矫,死亡現(xiàn)場離奇詭異抹沪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瓤球,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門融欧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卦羡,你說我怎么就攤上這事噪馏。” “怎么了绿饵?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵欠肾,是天一觀的道長。 經(jīng)常有香客問我蝴罪,道長董济,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任要门,我火速辦了婚禮虏肾,結(jié)果婚禮上廓啊,老公的妹妹穿的比我還像新娘。我一直安慰自己封豪,他們只是感情好谴轮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吹埠,像睡著了一般第步。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缘琅,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天粘都,我揣著相機(jī)與錄音,去河邊找鬼刷袍。 笑死翩隧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的呻纹。 我是一名探鬼主播堆生,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雷酪!你這毒婦竟也來了淑仆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤哥力,失蹤者是張志新(化名)和其女友劉穎蔗怠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體省骂,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蟀淮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了钞澳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怠惶。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖轧粟,靈堂內(nèi)的尸體忽然破棺而出策治,到底是詐尸還是另有隱情,我是刑警寧澤兰吟,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布通惫,位于F島的核電站,受9級特大地震影響混蔼,放射性物質(zhì)發(fā)生泄漏履腋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望遵湖。 院中可真熱鬧悔政,春花似錦、人聲如沸延旧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迁沫。三九已至芦瘾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間集畅,已是汗流浹背近弟。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留牡整,地道東北人藐吮。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像逃贝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子迫摔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容