iOS開發(fā)——cache自動(dòng)清理方案探索

有效的本地 cache 機(jī)制,可以避免不必要的重復(fù)網(wǎng)絡(luò)加載,不僅能提高相關(guān)應(yīng)用場(chǎng)景的資源加載速度,也可以避免不必要的流量浪費(fèi)造成用戶損失。但是,由于緩存一般做法是通過 url 經(jīng)過 md5 變換的值作為 key 進(jìn)行存儲(chǔ),因此對(duì)于同樣 url 的資源在第一次緩存之后如果沒有合適的清理機(jī)制,就會(huì)存在不同步的問題,導(dǎo)致 bug 的出現(xiàn)。本文就是再這樣的背景下,通過對(duì)比 NSURLCache、SDImageCache、YYCache、AFNetworking 等優(yōu)秀開源庫,探索通用的 cache 自動(dòng)清理方案。

NSURLCache 中的緩存清理方案

關(guān)于 NSURLCache,可以閱讀 這篇文章,其中詳細(xì)介紹了 url loading 系統(tǒng)中最關(guān)鍵的緩存部分。

緩存策略 NSURLRequestCachePolicy
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    NSURLRequestUseProtocolCachePolicy = 0,// 對(duì)特定的 URL 請(qǐng)求使用網(wǎng)絡(luò)協(xié)議中實(shí)現(xiàn)的緩存邏輯。默認(rèn)策略
    NSURLRequestReloadIgnoringLocalCacheData = 1,//數(shù)據(jù)需要從原始地址加載。不使用現(xiàn)有緩存
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // 不僅忽略本地緩存,同時(shí)也忽略代理服務(wù)器或其他中間介質(zhì)目前已有的、協(xié)議允許的緩存(未實(shí)現(xiàn))
    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
    NSURLRequestReturnCacheDataElseLoad = 2,// 無論緩存是否過期,先使用本地緩存數(shù)據(jù)。如果緩存中沒有請(qǐng)求所對(duì)應(yīng)的數(shù)據(jù),那么從原始地址加載數(shù)據(jù)。
    NSURLRequestReturnCacheDataDontLoad = 3,// 無論緩存是否過期,先使用本地緩存數(shù)據(jù)。如果緩存中沒有請(qǐng)求所對(duì)應(yīng)的數(shù)據(jù),那么放棄從原始地址加載數(shù)據(jù),請(qǐng)求視為失敗(即:“離線”模式)
    NSURLRequestReloadRevalidatingCacheData = 5, // 從原始地址確認(rèn)緩存數(shù)據(jù)的合法性后,緩存數(shù)據(jù)就可以使用,否則從原始地址加載(未實(shí)現(xiàn))
};

SDImageCache 中的緩存清理方案

SDImageCache 是優(yōu)秀的第三方網(wǎng)絡(luò)圖片下載庫 SDWebImage 中使用的自定義 cache 類,也是該庫的重要組件之一。功能包括了常見的緩存存儲(chǔ)、查詢以及清除,通過閱讀 SDImageCache 的源碼,就可以很快找到 cache 自動(dòng)清理的處理方案。

cache 組織結(jié)構(gòu)

SDImageCache 由內(nèi)存緩存和磁盤緩存兩部分組成,內(nèi)存緩存使用 NSCache 實(shí)現(xiàn),磁盤緩存通過NSFileManager的單例來管理,簡(jiǎn)單易懂。日常開發(fā)中,當(dāng)我們遇到 cache 相關(guān)的需求時(shí),其實(shí)可以直接使用 SDImageCache 而不需要重新造輪子。

閱讀頭文件,有這樣唯一一個(gè) designated 初始化方法:

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;

由此可知,SDImageCache 的初始化需要指定一個(gè)磁盤目錄作為緩存的根目錄和一個(gè)文件存儲(chǔ)的目錄,通過命名空間機(jī)制就可以把不同應(yīng)用場(chǎng)景的緩存目錄給隔離開,使用該機(jī)制最大的好處就在于,每一個(gè)自定的 cache 實(shí)例,包括該類的單例,都可以自動(dòng)管理自己存儲(chǔ)路徑下的緩存文件,而不會(huì)對(duì)其他人的 cache 有所影響。

cache 清理機(jī)制

先說說內(nèi)存緩存,由于 SDImageCache 使用 NSCache 來充當(dāng)內(nèi)存緩存,而
NSCache 本身就支持內(nèi)存清理機(jī)制,當(dāng)系統(tǒng)內(nèi)存很低時(shí)該類的實(shí)例會(huì)自動(dòng)釋放一些對(duì)象(模擬器除外),因此理論上不需要做什么額外的處理。該類有兩個(gè)可供開發(fā)者設(shè)置的屬性:

// 內(nèi)存總消耗限制,If 0, there is no total cost limit. The default value is 0.
@property NSUInteger totalCostLimit;

// 內(nèi)存總數(shù)量限制,If 0, there is no count limit. The default value is 0.
@property NSUInteger countLimit;

官方文檔也對(duì)這兩個(gè)屬性做了清晰的解釋,不過,SDImageCache 中采用的就是默認(rèn)值,另外,在 NSCache 子類的初始化方法中監(jiān)聽了系統(tǒng)內(nèi)存警告的通知,當(dāng)系統(tǒng)收到內(nèi)存警告時(shí),清空內(nèi)存中所有的對(duì)象。內(nèi)存清理 SDImageCache 就做了這么多。

下面說一下磁盤緩存。在 designated 初始化中,發(fā)現(xiàn) cache 實(shí)例監(jiān)聽了如下幾個(gè)通知:

[[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(deleteOldFiles)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];

第一個(gè)通知應(yīng)該有些多余,內(nèi)存部分已經(jīng)做了處理。仔細(xì)觀察處理后兩個(gè)通知的 selector 名大概能猜到,SDImageCache 在 app 將要被終止時(shí)和切后臺(tái)時(shí)都會(huì)做一次較舊文件的清除操作。

那么,什么樣的文件算是 oldFiles?
在 SDImageCache 的 designated 初始化方法中,有這樣一個(gè)被初始化的變量:

_config = [[SDImageCacheConfig alloc] init];

這個(gè)變量對(duì)應(yīng)的是頭文件中只讀的屬性:

@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;

翻遍 SDImageCache.m 文件可以發(fā)現(xiàn),這個(gè)屬性正是用于輔助處理磁盤緩存的清理操作的,如果外部沒有修改這個(gè)屬性,那么 SDImageCache 就會(huì)使用配置的默認(rèn)值進(jìn)行處理。

// 圖片下載完成后,是否解壓,默認(rèn) YES
@property (assign, nonatomic) BOOL shouldDecompressImages;

// 是否禁用 iclould 備份,默認(rèn) YES
@property (assign, nonatomic) BOOL shouldDisableiCloud;

// 是否使用內(nèi)存緩存,默認(rèn) YES
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;

// 磁盤緩存文件的最大有效期,單位 second
@property (assign, nonatomic) NSInteger maxCacheAge; 

// 最大緩存大小,單位 byte。默認(rèn)0,不會(huì)基于緩存大小對(duì)磁盤緩存進(jìn)行清理。
@property (assign, nonatomic) NSUInteger maxCacheSize;

具體如何清理,就要進(jìn)入 deleteOldFiles 方法以及 backgroundDeleteOldFiles 方法中查看。閱讀源碼發(fā)現(xiàn),最終都是調(diào)用這個(gè)方法:

- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;

根據(jù)時(shí)間有效性清理的步驟,有這樣一段注釋:

// Enumerate all of the files in the cache directory.  This loop has two purposes:
        //
        //  1. Removing files that are older than the expiration date.
        //  2. Storing file attributes for the size-based cleanup pass.

根據(jù)緩存大小限制清理的步驟,有這樣一段注釋:

// If our remaining disk cache exceeds a configured maximum size, perform a second
        // size-based cleanup pass.  We delete the oldest files first.
// Target half of our maximum cache size for this cleanup pass.

在遍歷緩存目錄時(shí),作者用到了 NSURL 的這個(gè)方法:

- (nullable NSDictionary<NSURLResourceKey, id> *)resourceValuesForKeys:(NSArray<NSURLResourceKey> *)keys error:(NSError **)error NS_AVAILABLE(10_6, 4_0);

針對(duì)每一個(gè)文件的 URL 鏈接,主要關(guān)注這幾個(gè)屬性:URL 是不是目錄的 path、URL 對(duì)應(yīng)文件最近修改時(shí)間以及文件分配的存儲(chǔ)空間,即代碼中的:NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
這里給大家留個(gè)思考問題,作者在計(jì)算文件大小的時(shí)候,不使用 NSURLFileAllocatedSizeKey,而是用 NSURLTotalFileAllocatedSizeKey,為什么?

小結(jié)一下,SDImageCache 緩存時(shí)效性問題的處理方案是:內(nèi)存緩存在遇到內(nèi)存警告時(shí)全部清除,磁盤緩存根據(jù)外部配置在客戶端終止前或者切到后臺(tái)時(shí)進(jìn)行清理,過期的緩存文件全部刪除,緩存總大小超過 maxSize時(shí),從最大的文件開始刪除直到當(dāng)前緩存大小在 maxSize/2 值以下。

YYCache 中的緩存清理方案

AFNetworking 中的緩存清理方案

AFNetworking 是專業(yè)的網(wǎng)絡(luò)請(qǐng)求框架,支持 NSURLConnection 和 NSURLSession 的請(qǐng)求方式,AFN 的下載緩存部分采用 NSURLCache,NSURLCache 是系統(tǒng)自動(dòng)管理緩存部分,就不多做介紹,這里主要介紹下其為圖片請(qǐng)求設(shè)計(jì)的 cache:AFAutoPurgingImageCache。

cache 組織結(jié)構(gòu)

AFAutoPurgingImageCache 只包括內(nèi)存緩存部分,所以實(shí)現(xiàn)也比較簡(jiǎn)單。內(nèi)存緩存使用 NSMutableDictionary 實(shí)現(xiàn)。

cache 清理機(jī)制

默認(rèn)最大內(nèi)存緩存大小為 100M,每次內(nèi)存超過最大值時(shí),清理掉 60M 的空間。清除內(nèi)存時(shí),按照 LUR 算法,首先對(duì)內(nèi)存中的圖片數(shù)據(jù)按照最近訪問時(shí)間進(jìn)行排序,優(yōu)先刪除最后訪問時(shí)間久遠(yuǎn)的數(shù)據(jù)。

小結(jié):網(wǎng)絡(luò)請(qǐng)求加載庫主要實(shí)現(xiàn)對(duì) NSURLConnection 和 NSURLSession 的封裝,緩存部分主要還是使用系統(tǒng)的 NSURLCache 實(shí)現(xiàn),重點(diǎn)關(guān)注下內(nèi)存緩存清理時(shí),如何像 AFNetworing 這樣采用 LRU 算法進(jìn)行清理,提高 cache 的命中率。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,238評(píng)論 3 428
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,339評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,713評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評(píng)論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,893評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,448評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,201評(píng)論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,397評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,631評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評(píng)論 1 293
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,128評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,347評(píng)論 2 377

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