iOS -- 構建緩存時選用 NSCache (32)

構建緩存時選用 NSCache

?開發 Mac OS X 或 iOS 應用程序時,經常會遇到一個問題,那就是從網上下載的圖片應如何來緩存,NSCache 類就是 Foundation 框架專為處理這種任務而設計的.

?NSCache 的優點在于,當系統資源將要耗盡時,它可以自動刪減緩存,此外,NSCache 還會先行刪減'最久未使用的對象'.?

另外,NSCache 是線程安全的,意思就是:在開發者自己不編寫加鎖代碼的前提下,多個線程便可以同時訪問 NSCache, 對緩存來說,線程安全非常重要,因為開發者可能要在某個線程中讀取數據,此時如果發現緩存里面找不到指定的鍵,那么就下載該鍵所對應的數據了,而下載完數據之后所要執行的回調函數,有可能會放在背景線程中運行, 這樣的話,就等于是用另外一個線程來寫入緩存 了.?

開發者可以操控緩存刪減其內容的時機.有兩個與系統資源相關的尺度可供調整, ?其一是緩存的對象總數, ?其二是所有對象的 '總開銷', 開發者在將對象加入緩存時, 可為其指定 '開銷值', 當對象總數 或 總開銷 超過上限時,緩存就可能會刪減其中的對象了, 在可用的系統資源趨于緊張時, 也會這么做,然而要注意, '可能'會刪減某個對象, 并不意味著 '一定'會刪減這個對象, 刪減對象時所遵循的順序, 由具體實現來定,這尤其說明: 想通過調整 '開銷值'來迫使緩存優先刪減某對象, 不是個好辦法.?

向緩存中添加對象時,只有在很快計算出 '開銷值'的情況下,才應該采用'總開銷'這個尺度,若是計算過程很復雜,那么照這種方式來使用緩存就達不到最佳效果了.因為每次向緩存 中放入對象,還要專門花時間來計算這個附加因素的值, 而緩存的本意則是要增加應用程序響應用戶操作的速度.比方說,如果計算'開銷值'時必須訪問磁盤才能確定文件大小, 或是必須訪問數據庫才能決定具體數值, 那就不好了, 但是,如果要加入緩存中的是 NSData 對象, 那么就不妨指定 '開銷值',可以把數據大小當做 '開銷值'來用,因為 NSData 對象的數據大小是 已知的,所及計算'開銷值'的過程只不過是讀取一項屬性. 例如:??

#import// 網絡訪問類

typedef void(^NetworkFetcherCompletionHandler)(NSData * data);

@interface NetworkFetcher : NSObject

- (id)initWithURL:(NSURL *)url;

- (void)startWithCompletionHandler:(NetworkFetcherCompletionHandler)handler;

@end

// 用戶網絡獲取和緩存結果的類

@interface NSCacheClass : NSObject

@end

@implementation NSCacheClass{

? ? ? ?NSCache * _cache;

}

- (id)init{

? ? ? ? if (self = [super init]){

? ? ? ? _cache = [NSCache new];

? ? ? ? // 緩存的最大值是 100 條 URL

? ? ? ?_cache.countLimit = 100;

? ? ? // 5MB 的 總開銷 限制. ??

? ? ? _cache.totalCostLimit = 5 * 1024 * 1024;

? ? ?}

? ? ? ?return self;

}

- (void)downloadDataForURL:(NSURL *)url{

? ? ? ? ? NSData * cachedData = [_cache objectForKey:url];

? ? ? ? ? if(cachedData){

? ? ? ? ? ? ? ? [self useData:cachedData];

? ? ? ? ? }else{

? ? ? ? ? ? ? ?NetworkFetcher * fetcher = [[NetworkFetcher alloc]initWithURL:url];

? ? ? ? ? ? ? ?[fetcher startWithCompletionHandler:^(NSData * data){

? ? ? ? ? ? ? ? ? ? ? ? ? [_cache setobject:data forKey:url cost:data.length];

? ? ? ? ? ? ? ? ? ? ? ? ? [self useData:data];

? ? ? ? ? ? ? ? }];

? ? ? ? ? ?}

}

@end

在本例中, 下載數據所用的 URL ,就是緩存的鍵, 若緩存不存在, 則下載數據并將其放入緩存, 而數據的'開銷值'則為其長度, 創建 NSCache 時,將其中可緩存的總對象數目上限設為 100,將'總開銷'上限設為 5MB ,由于'開銷量'以字節為單位, 所以要通過算式將 MB 換算成 字節.

還有個類叫做 NSPurgeableData (可清除的 data), 和 NSCache 搭配起來用, 效果很好,此類是 NSMutableData 的子類,而且實現 NSDiscardableContent 協議(可丟棄的內容協議), 如果某個對象所占的內存能夠根據需要隨時丟棄, 那么就可以實現該協議所定義的接口, 這就是說, 當系統資源緊張時, 可以把保存 NSPurgeableData 對象的那塊內存釋放掉, NSDiscardableContent 協議里面定義了名為 isContentDiscarded 的方法, 可以用來查詢相關內存是否已經釋放.

如果需要訪問某個 NSPuargeableData 對象, 可以調用其 beginContentAccess 方法, 告訴它現在還不應丟棄自己所占據的內存, 用完之后, 調用 endContentAccess 方法, 告訴它在必要的時候可以丟棄自己所占據的那塊內存, 這些調用可以嵌套, 所以說,衙門就像遞增遞減引用計數一樣,只有對象的 '引用計數'為 0 的時候才可以丟棄.

如果將 NSPurgeableData 對象加入 NSCathe ,那么當該對象為系統所丟棄時, 也會自動從緩存中移除, 通過 NSCache 的 evictsObjectsWithDiscardedContent (移除廢棄的內容) 屬性, 可以開啟或關閉此 功能.

剛才的例子可以改寫為:

- (void)downloadDataForURL:(NSURL *)url{

? ? ? ? ? ? NSPurgeableData * cachedData = [_cache objectForKey:url];

? ? ? ? ? ? ?if(cachedData){

? ? ? ? ? ? ? ? ? ? [cacheData beginContentAccess];

? ? ? ? ? ? ? ? ? ? [self useData:cachedData];

? ? ? ? ? ? ? ? ? ? [cacheData endContentAccess];

? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? ? ?NetworkFetcher * fetcher = [[NetworkFetcher alloc]initWithURL:url];

? ? ? ? ? ? ? ? ? [fetcher startWithCompletionHandler:^(NSData * data){

? ? ? ? ? ? ? ? ? ? ? ? ? ? NSPurgeableData * purgeableData = [NSPurgeableData dataWithData:data];

? ? ? ? ? ? ? ? ? ? ? ? ? ?[_cache setobject:data forKey:url cost:purgeableData.length];

? ? ? ? ? ? ? ? ? ? ? ? ? ?[self useData:data];

? ? ? ? ? ? ? ? ? ? ? ? ? ?[purgeableData endContentAccess];

? ? ? ? ? ? ? ? ? }];

? ? ? ? ? ?}

}

注意: 創建好了 NSPurgeableData 對象之后, 其 'purge 引用計數' 會多一, 所以無須再調用 beginContentAccess 了, 然而其后必須調用 endContentAccess ,將多出來的 "1" 抵消.

總結:

NSCathe 提供了自動刪減功能, 而且是'線程安全的'.

可以給 NSCache 對象設置上限, 用以限制緩存中的對象總個數 及 '總成本',而這些尺度則定義了 緩存刪減其中對象的時機. 但是絕對不要把這些尺度當成可靠的 '硬限制',它們僅對 NSCache 其指導作用.

將 NSPurgeableData 與 NSCache 搭配使用, 可實現自動清除數據的功能, 也就是說, 當 NSPurgeableData 對象所占據的內存為系統所丟棄時, 該對象自身也會從緩存中移除.

如果緩存使用得當, 那么應用程序的響應速度就能提高, 只有那種 '重新計算起來很麻煩的'數據,才值得放入緩存, 比如那些需要從網絡獲取或 從 磁盤讀取的數據.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內容