iOS源碼(三)YYCache

注:本文最好是配著代碼一起閱讀,如果我把代碼加到文章里面,篇幅太大。

YYCache的一些基本方法:
//根據(jù)名稱或者路徑獲取YYCache對(duì)象

  • (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;
    //存入對(duì)象
  • (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;

根據(jù)名稱或者路徑獲取yycache對(duì)象:
如果是傳入名稱,就獲取Documents目錄路徑和傳入的名稱進(jìn)行拼接來生成緩存路徑,如果是傳入的路徑,則開始進(jìn)行緩存初始化的操作。(錯(cuò)誤檢測(cè):傳入的path是否為空)
1、YYDiskCache的初始化(傳入路徑),2、根據(jù)路徑獲取名稱,3、初始化YYMemoryCache。4、賦值

YYDiskCache的初始化:初始化有路徑和一個(gè)20kb的數(shù)值類型(方法需要傳入緩存路徑和緩存閾值threshold參數(shù)。在作者設(shè)計(jì)思路文章中分析到,超過20k數(shù)據(jù)使用文件緩存讀寫快,而低于20k數(shù)據(jù)使用數(shù)據(jù)庫讀寫比較快),初始化方法里面:1、YYDiskCache的初始化,2、YYKVStorageType的初始化,3、YYKVStorage的初始化,4、方法- (void)_trimRecursively;(在初始化的時(shí)候調(diào)用_trimRecursively方法每隔60s時(shí)間檢測(cè)一下緩存數(shù)據(jù)大小是否超過容量。),5、創(chuàng)建程序終止的監(jiān)聽。(錯(cuò)誤檢測(cè):以上每一個(gè)初始化如果依靠返回的值的話,如果為空則直接return nil。)

上述1中,YYDiskCache于_YYDiskCacheGetGlobal,_YYDiskCacheGetGlobal首先單例初始化一個(gè)static NSMapTable *_globalInstances;和static dispatch_semaphore_t _globalInstancesLock; 然后在_globalInstances里取值cache,取值過程利用信號(hào)量(_globalInstancesLock)來保證線程安全。然后返回cache賦值給YYDiskCache對(duì)象。

上述2:根據(jù)傳入的數(shù)值的大小來初始化存儲(chǔ)類型,三種類型(YYKVStorageTypeFile = 0,
YYKVStorageTypeSQLite = 1,YYKVStorageTypeMixed = 2,)

上述3:YYKVStorage的初始化會(huì)傳入路徑和存儲(chǔ)類型,進(jìn)入初始化函數(shù)(錯(cuò)誤判斷:會(huì)判斷傳入的路徑和類型。),在初始化函數(shù)里會(huì)根據(jù)傳入名稱,數(shù)據(jù)名稱和廢棄的垃圾名稱來創(chuàng)建NSFileManager文件對(duì)象。然后會(huì)判斷數(shù)據(jù)庫是否打開和是否初始化過(算是一種容錯(cuò)的操作)。然后進(jìn)入方法_fileEmptyTrashInBackground(如果上次失敗,清空垃圾,并且會(huì)將清除操作放在異步隊(duì)列里進(jìn)行)

上述5:程序終止的監(jiān)聽,接收到的時(shí)候會(huì)把YYKVStorage對(duì)象賦值為nil。

存入對(duì)象:雙緩存
先存內(nèi)存,然再存磁盤。
存內(nèi)存:模擬NSCache的方法,傳入對(duì)象,key值然后封裝成傳入對(duì)象,key值,大小cost。- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost; 方法內(nèi)部(錯(cuò)誤判斷:key為空則返回nil,對(duì)象為空則根據(jù)key移除存儲(chǔ)對(duì)象),存儲(chǔ)過程中利用pthread_mutex_lock互斥所保證線程安全,存儲(chǔ)過程:主要是模擬lru,利用CFDictionaryGetValue方法從_YYLinkedMap *_lru;(對(duì)象定義看注解①)里面的字典對(duì)象根據(jù)key值遍歷查找,結(jié)果返回并賦值給_YYLinkedMapNode *node(對(duì)象定義看注解②),如果找到則重新賦值,未找到則創(chuàng)建node并重新賦值,然后放置lru的鏈表頭部。然后進(jìn)行緩存大小是否超過限制的判斷,如果超過則異步進(jìn)行清理操作(- (void)_trimToCost:(NSUInteger)costLimit;(詳情可看注解③)),然后再進(jìn)行判斷。(內(nèi)存的清理可以設(shè)置成在主線程里清除或者異步清除)。

存磁盤:老規(guī)矩,先來一手錯(cuò)誤的判斷,判斷key值和對(duì)象的值是否為空,然后根據(jù)YYKVStorage里的kv對(duì)象來進(jìn)行操作([_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];),這個(gè)過程加鎖,依舊是判斷傳入的值是否錯(cuò)誤,然后根據(jù)存儲(chǔ)類型來進(jìn)行sqlite還是文件磁盤的存儲(chǔ)。

讀取緩存:雙緩存
老規(guī)矩,先從YYMemoryCache內(nèi)存里讀取,內(nèi)存讀取方法里面(錯(cuò)誤判斷傳入的key是否為空)用pthread_mutex_lock互斥鎖來保證線程安全,然后用CFDictionaryGetValue方法來遍歷lru里面的鏈表,如果內(nèi)存找到先把找到的對(duì)象的節(jié)點(diǎn)放于表頭然后返回,如果未找到則進(jìn)入磁盤查找。

移除緩存
依舊是錯(cuò)誤的判斷以及加鎖,如果鏈表尾部或者是根據(jù)CFDictionaryGetValue找到lru相應(yīng)的節(jié)點(diǎn)并刪除,還有就是獲取的對(duì)象在主線程釋放還是異步釋放的判斷。

一些解釋:
NSMapTable: NSMapTable和NSDictionary相對(duì)應(yīng),相對(duì)于NSDictionary/NSMutableDictionary,NSMapTable有如下的特征: NSDictionary/NSMutableDictionary會(huì)copy對(duì)應(yīng)的key,強(qiáng)引用相應(yīng)的value。 NSMapTable是可變的,沒有一個(gè)不變的類與其對(duì)應(yīng)。 NSMapTable可以對(duì)其key和value弱引用,在這種情況下當(dāng)key或者value被釋放的時(shí)候,此entry會(huì)自動(dòng)從NSMapTable中移除。 NSMapTable在加入一個(gè)(key,value)的時(shí)候,可以對(duì)其value設(shè)置為copy。 NSMapTable可以包含任意指針,使用指針去做相等或者h(yuǎn)ashing檢查。
dispatch_semaphore_t:信號(hào)量,用于線程同步。

@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;
流程:同樣的互斥鎖加鎖,并用一個(gè)bool值finsh來判斷是否完成清理然后解鎖。清理的過程主要是移除lru鏈表尾部的對(duì)象(首先是移除尾部,然后把尾部的對(duì)象放在一個(gè)可變數(shù)組里,最后去判斷在主線程里清理還是異步清理,并在串行隊(duì)列里面去釋放這個(gè)可變數(shù)組里面的對(duì)象)

關(guān)于
_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上調(diào)用對(duì)象的方法
這種寫法的解釋:應(yīng)該是node在執(zhí)行完這個(gè)方法后就出了作用域了,reference會(huì)減1,但是此時(shí)node不會(huì)被dealloc,因?yàn)閎lock 中retain了node,使得node的reference count為1,當(dāng)執(zhí)完block后,node的reference count又-1,此時(shí)node就會(huì)在block對(duì)應(yīng)的queue上release了。

YYDiskCache里用dispatch_semaphore_wait二不是用OSSpinLockLock的原因:DiskCache 鎖占用時(shí)間可能會(huì)比較長,如果用 SpinLock 會(huì)在鎖存在競(jìng)爭(zhēng)時(shí)占用大量 CPU 資源。

特定線程釋放資源的解釋:避免了過多線程導(dǎo)致的性能問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 今天開始分析YYCache 包含的文件類 YYCache YYMemoryCache YYDiskCache YY...
    充滿活力的早晨閱讀 921評(píng)論 4 1
  • 從 YYCache 源碼 Get 到如何設(shè)計(jì)一個(gè)優(yōu)秀的緩存 來源:Lision 前言 iOS 開發(fā)中總會(huì)用到各種緩...
    今天lgw閱讀 6,282評(píng)論 1 22
  • YYCache是用于Objective-C中用于緩存的第三方框架。此文主要用來講解該框架的實(shí)現(xiàn)細(xì)節(jié),性能分析、設(shè)計(jì)...
    JonesCxy閱讀 690評(píng)論 0 2
  • 概述 上一篇主要講解了YYCache的文件結(jié)構(gòu),分析了YYCache類的相關(guān)方法,本章主要分析內(nèi)存緩存類YYMem...
    egoCogito_panf閱讀 3,278評(píng)論 2 12
  • 前言 日常的iOS開發(fā)過程中,經(jīng)常會(huì)用到緩存,但是什么樣的緩存才能被叫做優(yōu)秀的緩存,或者說優(yōu)秀的緩存應(yīng)該具備哪些特...
    雨潤聽潮閱讀 2,580評(píng)論 0 2

友情鏈接更多精彩內(nèi)容