深知,源碼仍是一點點讀,加點讀書筆記,才能夠深刻挖掘,所以仍是以爲每次讀源碼都記錄一番,不管好壞,若有寫錯,請斧正node
YYCahce 是做爲 ibireme 大神開源的一個YYkit組件庫中的一部分,YYCache提供了內存緩存,和持久性的硬盤緩存。ios
YYCache 提供對外的整合接口,YYMemoryCache 提供內存存儲緩存,經過lru算法進行處理,YYDiskCache提供file和sqlite3的兩種持久化存儲方式算法
YYMemoryCahce 做爲一個內存緩存,提供高速緩存,而且由於內存有限,須要進行必定的限制sql
線程安全數據庫
在頻率高的併發數據操做中,必須保證線程安全,否則拿到的數據不符合預期,或者致使讀寫衝突 在YYMemoryCache中,採用的pthread_mutex加鎖api
pthread_mutex 定義了一組跨平臺的線程相關的 API,pthread_mutex 表示互斥鎖。互斥鎖的實現原理與信號量很是類似,不是使用忙等,而是阻塞線程並睡眠,須要進行上下文切換。緩存
// 主要使用api
pthread_mutex_lock(&mutex); // 申請鎖
// 臨界區
pthread_mutex_unlock(&mutex); // 釋放鎖
複製代碼
pthread_mutex支持遞歸鎖,遞歸鎖,就是在內部加鎖調用的時候,遞歸了自身,這樣子,會致使死鎖。安全
pthread_mutex則支持遞歸處理 attr PTHREAD_MUTEX_RECURSIVEbash
memory中使用pthread,在循環釋放的時候,經過pthread_mutex_trylock實現簡單的自旋鎖數據結構
原文:OSSpinLock 和 dispatch_semaphore 都不會產生特別明顯的死鎖,因此我也沒法肯定用 dispatch_semaphore 代替 OSSpinLock 是否正確。可以確定的是,用 pthread_mutex 是安全的。
LRU
最近使用優先,也就是認爲,最近使用的,最大可能性會再次用到 裏面實現用到一個雙向鏈表的結構進行處理,使用到的就會被移動到表頭 在刪除釋放的時候,就會從隊尾進行刪除釋放
經過建立鏈表節點node,進行間接操做數據
#pragma mark - _YYLinkedMapNode
@interface _YYLinkedMapNode : NSObject {
// 相似C中的private_extern,使用@private的話,限制太大,@package在類的鏡像外進行引用就會報錯
// 使用@public @protect等的話,就沒什麼限制的
// 目的是,限制在本文件中使用
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // 經過dic進行持有
__unsafe_unretained _YYLinkedMapNode *_next; // 經過dic進行持有
id _key;
id _value;
NSUInteger _cost;
NSTimeInterval _time;
}
@end
複製代碼
// 限制條件有3個,age cost count ,通常狀況下默認都不作限制
/// 個數限制
@property NSUInteger countLimit;
/// 過時時間
@property NSTimeInterval ageLimit;
/// 存儲消耗限制,setObject的時候時候把內存大小的存進去
@property NSUInteger costLimit;
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground; // 進入後臺檢查限制刪除緩存,默認YES
@property BOOL shouldRemoveAllObjectsOnMemoryWarning; // 收到內存警告的時候
複製代碼
MemoryCache中使用的LRU的的算法進行刪除緩存,當超過必定的限制的時候,會進行循環清理,也就是說,memory裏面的東西並非存進去就會在app的生命週期中一直存在的,可能會被釋放掉。所以外部在使用的時候,須要判空處理的,若是爲nil,則認爲緩存已經失效,須要從新更新。
所以,YYMemoryCahce並非數據在app的生命週期會被一直保留的,因此,在使用YYCahce最外層接口的時候,YYCache是會經過YYMemoryCache提供高速緩存,同時存入到低速緩存中。
獲取的時候,YYMemoryCache獲取不到,則會詢問YYDiskCache。
會在一段時間,自行檢查是否超過限制策略,超過則循環釋放。 YYMemoryCache默認在進入後臺的時候會進行檢查。 由於一次釋放太多,會致使資源消耗過大,所以,經過dispatch_aync block的方式,放到後臺線程釋放
disk中使用dispatch_semaphore信號量進行處理
dispatch_semaphore 是信號量,但當信號總量設爲 1 時也能夠看成鎖來。在沒有等待狀況出現時,它的性能比 pthread_mutex 還要高,但一旦有等待狀況出現時,性能就會降低許多。相對於 OSSpinLock 來講,它的優點在於等待時不會消耗 CPU 資源。對磁盤緩存來講,它比較合適。
dispatch_semaphore是屬於閒等待,CPU不會消耗,所以,在作磁盤緩存的時候,用時較長,須要等待的話,會比較節省資源
YYKVStorage 經過key value的一個封裝,實現增刪改查
YYKVStorage 實現細節:
- (BOOL)_dbClose {
// 關閉數據庫
if (!_db) {
return YES;
}
int result = 0;
BOOL retry = NO;
BOOL stmtFinalized = NO;
if (_dbStmtCache) {
_dbStmtCache = NULL;
}
do {
retry = NO;
result = sqlite3_close(_db);
if (result == SQLITE_BUSY || result == SQLITE_LOCKED) {
// 若是sqlite被佔用,那進行stmt的析構操做,讓其釋放資源
// 在從新試一次
if (!stmtFinalized) {
stmtFinalized = YES; //析構
sqlite3_stmt *stmt;
// sqlite3_stmt 是一種輔助數據結構,用於操做二進制數據
// 循環釋放全部的sqlite stmt
while ((stmt = sqlite3_next_stmt(_db, nil))) {
sqlite3_finalize(stmt); //析構 全部的sqlite stmt的輔助
retry = YES; // 而後重試,
}
}
} else if (result != SQLITE_OK) {
// 關閉失敗
// 輸出 錯誤日誌
NSLog(@"關閉失敗");
}
} while (retry);
_db = NULL;
return YES;
}
- (BOOL)_dbCheck {
if (_db) {
// 若是重試錯誤的次數小於限定次數,而且大於最小重試時間,則從新進行打開和初始化檢查
if (_dbOpenErrorCount < kMaxErrorRetryCount &&
CACurrentMediaTime() - _dbLastOpenErrorTime > kMinRetryTimeInterval) {
return [self _dbOpen] && [self _dbInitialize];
} else {
return NO;
}
}
return YES; // 正常
}
複製代碼
// 獲取緩存中的stmt,用sql作key
- (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {
if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL;
sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));
if (!stmt) {
// 若是stmt爲空的話
int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
if (result != SQLITE_OK) {
// 若是不成功
NSLog(@"建立stmt失敗");
return NULL; // 返回空
}
CFDictionarySetValue(_dbStmtCache, sql.UTF8String, stmt); // 緩存起來,都是同一個stmt,每次建立的話,會大量消耗資源,所以這裏作了緩存
} else {
sqlite3_reset(stmt);
}
return stmt;
}
複製代碼
存儲方式有三個模式
db sqlite3的使用細節
優化: 能夠升級最新版本的sqlite3,以此提升效率
提供了增刪改查的API,底下調用的YYMemoryCache和YYDiskCache,封裝了一些基本邏輯。在save的時候,會保存到YYMemoryCache和YYDiskCache,讀取的時候,會優先讀取YYMemoryCache實現高速緩存,再讀取低速緩存。
實際上在看的時候,下面這位大佬寫得更加清晰的,之因此我按本身的理解再寫一次,也是爲了讓本身更好的研究,根據全部學習技巧來講,最重要的仍是應用,所以有了這篇文章,但願以後能逐漸寫出一些更好blog
從 YYCache 源碼 Get 到如何設計一個優秀的緩存 - https://juejin.im/post/59f6e3b051882534af253d4a
ibireme blog - http://blog.ibireme.com/category/tec/ios-tec/