YYCache深刻學習

深知,源碼仍是一點點讀,加點讀書筆記,才能夠深刻挖掘,所以仍是以爲每次讀源碼都記錄一番,不管好壞,若有寫錯,請斧正node

簡介

YYCahce 是做爲 ibireme 大神開源的一個YYkit組件庫中的一部分,YYCache提供了內存緩存,和持久性的硬盤緩存。ios

一個合理緩存應該有的設計

  • 合理的增刪改查接口
  • 高速緩存,提升經常使用緩存的返回性能和效率
  • 低速緩存,磁盤大文件緩存
  • 良好緩存限制策略
  • 高性能,線程安全

基本設計思路

YYCache 提供對外的整合接口,YYMemoryCache 提供內存存儲緩存,經過lru算法進行處理,YYDiskCache提供file和sqlite3的兩種持久化存儲方式算法

YYMemoryCache

YYMemoryCahce 做爲一個內存緩存,提供高速緩存,而且由於內存有限,須要進行必定的限制sql

  1. 線程安全數據庫

    在頻率高的併發數據操做中,必須保證線程安全,否則拿到的數據不符合預期,或者致使讀寫衝突 在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 是安全的。

  2. LRU

最近使用優先,也就是認爲,最近使用的,最大可能性會再次用到 裏面實現用到一個雙向鏈表的結構進行處理,使用到的就會被移動到表頭 在刪除釋放的時候,就會從隊尾進行刪除釋放

  1. 鍵值對存儲操做

經過建立鏈表節點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
複製代碼
  1. 限制策略
// 限制條件有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。

  1. 某些小技巧,用於提供性能

會在一段時間,自行檢查是否超過限制策略,超過則循環釋放。 YYMemoryCache默認在進入後臺的時候會進行檢查。 由於一次釋放太多,會致使資源消耗過大,所以,經過dispatch_aync block的方式,放到後臺線程釋放


YYDiskCache

  1. 線程安全

disk中使用dispatch_semaphore信號量進行處理

dispatch_semaphore 是信號量,但當信號總量設爲 1 時也能夠看成鎖來。在沒有等待狀況出現時,它的性能比 pthread_mutex 還要高,但一旦有等待狀況出現時,性能就會降低許多。相對於 OSSpinLock 來講,它的優點在於等待時不會消耗 CPU 資源。對磁盤緩存來講,它比較合適。

dispatch_semaphore是屬於閒等待,CPU不會消耗,所以,在作磁盤緩存的時候,用時較長,須要等待的話,會比較節省資源

  • OSSpinLock 自旋鎖,不安全,因此使用了dispatch_semaphore進行處理,由於大文件,須要等待的狀況較多
  1. 大文件處理
  • YYKVStorage 經過key value的一個封裝,實現增刪改查

  • YYKVStorage 實現細節:

    • 提供對應的sqlite3的db增刪改查的接口,在操做的過程都作了防護式的操做
    - (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; // 正常
    }
    複製代碼
    • 對sqlite的stmt也作了緩存,加速其性能
    // 獲取緩存中的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;
    }
    複製代碼
  • 存儲方式有三個模式

    • 文件
    • sqlite3
    • 混合 // 混合的狀況有個閾值,大於閾值,則存文件
  • db sqlite3的使用細節

    • sqlite3_stmt 操做數據的輔助數據接口,用於執行sql,而且返回結果
    • stmt 也進行作了緩存,由於這個sql會重複不按期使用
  • 優化: 能夠升級最新版本的sqlite3,以此提升效率


YYCache

提供了增刪改查的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/

相關文章
相關標籤/搜索