NSCache 原理 (著名開源框架SDWebImage 就是使用的NSCache實現的緩存)

NSCache是一個類似於集合的容器,即緩存。它存儲key-value對,這一點類似於NSDictionary類。我們通常用使用緩存來臨時存儲短時間使用但創建昂貴的對象。重用這些對象可以優化性能,因爲它們的值不需要重新計算。另外一方面,這些對象對於程序來說不是緊要的,在內存緊張時會被丟棄。如果對象被丟棄了,則下次使用時需要重新計算。

當一個key-value對在緩存中時,緩存維護它的一個強引用。存儲在NSCache中的通用數據類型通常是實現了NSDiscardableContent協議的對象。在緩存中存儲這類對象是有好處的,因爲當不再需要它時,可以丟棄這些內容,以節省內存。默認情況下,緩存中的NSDiscardableContent對象在其內容被丟棄時,會被移除出緩存,儘管我們可以改變這種緩存策略。如果一個NSDiscardableContent被放進緩存,則在對象被移除時,緩存會調用discardContentIfPossible方法。

NSCache與可變集合有幾點不同:

  1. NSCache類結合了各種自動刪除策略,以確保不會佔用過多的系統內存。如果其它應用需要內存時,系統自動執行這些策略。當調用這些策略時,會從緩存中刪除一些對象,以最大限度減少內存的佔用。
  2. NSCache是線程安全的,我們可以在不同的線程中添加、刪除和查詢緩存中的對象,而不需要鎖定緩存區域。
  3. 不像NSMutableDictionary對象,一個緩存對象不會拷貝key對象。

這些特性對於NSCache類來說是必須的,因爲在需要釋放內存時,緩存必須異步地在幕後決定去自動修改自身。

緩存限制

NSCache提供了幾個屬性來限制緩存的大小,如屬性countLimit限定了緩存最多維護的對象的個數。聲明如下:

@property NSUInteger countLimit

默認值爲0,表示不限制數量。但需要注意的是,這不是一個嚴格的限制。如果緩存的數量超過這個數量,緩存中的一個對象可能會被立即丟棄、或者稍後、也可能永遠不會,具體依賴於緩存的實現細節。

另外,NSCache提供了totalCostLimit屬性來限定緩存能維持的最大內存。其聲明如下:

@property NSUInteger totalCostLimit

默認值也是0,表示沒有限制。當我們添加一個對象到緩存中時,我們可以爲其指定一個消耗(cost),如對象的字節大小。如果添加這個對象到緩存導致緩存總的消耗超過totalCostLimit的值,則緩存會自動丟棄一些對象,直到總消耗低於totalCostLimit值。不過被丟棄的對象的順序無法保證。

需要注意的是totalCostLimit也不是一個嚴格限制,其策略是與countLimit一樣的。

存取方法

NSCache提供了一組方法來存取key-value對,類似於NSMutableDictionary類。如下所示:

- (id)objectForKey:(id)key

- (void)setObject:(id)obj forKey:(id)key

- (void)removeObjectForKey:(id)key

- (void)removeAllObjects

如上所述,與NSMutableDictionary不同的就是它不會拷貝key對象。

此外,我們在存儲對象時,可以爲對象指定一個消耗值,如下所示:

- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)num

這個消耗值用於計算緩存中所有對象的一個消耗總和。當內存受限或者總消耗超過了限定的最大總消耗,則緩存應該開啓一個丟棄過程以移除一些對象。不過,這個過程不能保證被丟棄對象的順序。其結果是,如果我們試圖操作這個消耗值來實現一些特殊的行爲,則後果可能會損害我們的程序。通常情況下,這個消耗值是對象的字節大小。如果這些信息不是現成的,則我們不應該去計算它,因爲這樣會使增加使用緩存的成本。如果我們沒有可用的值傳遞,則直接傳遞0,或者是使用-setObject:forKey:方法,這個方法不需要傳入一個消耗值。

NSDiscardableContent協議

NSDiscardableContent是一個協議,實現這個協議的目的是爲了讓我們的對象在不被使用時,可以將其丟棄,以讓程序佔用更少的內存。

一個NSDiscardableContent對象的生命週期依賴於一個“counter”變量。一個NSDiscardableContent對象實際是一個可清理內存塊,這個內存記錄了對象當前是否被其它對象使用。如果這塊內存正在被讀取,或者仍然被需要,則它的counter變量是大於或等於1的;當它不再被使用時,就可以丟棄,此時counter變量將等於0。當counter變量等於0時,如果當前時間點內存比較緊張的話,內存塊就可能被丟棄。

爲了丟棄這些內容,可以調用對象的discardContentIfPossible方法,該方法的聲明如下:

- (void)discardContentIfPossible

這樣當counter變量等於0時將會釋放相關的內存。而如果counter變量不爲0,則該方法什麼也不做。

默認情況下,NSDiscardableContent對象的counter變量初始值爲1,以確保對象不會被內存管理系統立即釋放。從這個點開始,我們就需要去跟蹤counter變量的狀態。爲此。協議聲明瞭兩個方法:beginContentAccess和endContentAccess。

其中調用beginContentAccess方法會增加對象的counter變量(+1),這樣就可以確保對象不會被丟棄。該方法聲明如下:

- (BOOL)beginContentAccess

通常我們在對象被需要或者將要使用時調用這個方法。具體的實現類可以決定在對象已經被丟棄的情況下是否重新創建這些內存,且重新創建成功後返回YES。協議的實現者在NSDiscardableContent對象被使用,而又沒有調用它的beginContentAccess方法時,應該拋出一個異常。

函數的返回值如果是YES,則表明可丟棄內存仍然可用且已被成功訪問;否則返回NO。另外需要注意的是,該方法是在實現類中必須實現(required)。

與beginContentAccess相對應的是endContentAccess。如果可丟棄內存不再被訪問時調用。其聲明如下:

- (void)endContentAccess

該方法會減少對象的counter變量,通常是讓對象的counter值變回爲0,這樣在對象的內容不再被需要時,就要以將其丟棄。

NSCache類提供了一個屬性,來標識緩存是否自動捨棄那些內存已經被丟棄的對象(discardable-content object),其聲明如下:

@property BOOL evictsObjectsWithDiscardedContent

如果設置爲YES,則在對象的內存被丟棄時捨棄對象。默認值爲YES。

NSCacheDelegate代理

NSCache對象還有一個代理屬性,其聲明如下:

@property(assign) id< NSCacheDelegate > delegate

實現NSCacheDelegate代理的對象會在對象即將從緩存中移除時執行一些特定的操作,因此代理對象可以實現以下方法:

- (void)cache:(NSCache *)cache willEvictObject:(id)obj

需要注意的是在這個代理方法中不能修改cache對象。

小結

實際上,我們常用的SDWebImage圖片下載庫的緩存機制就是通過NSCache來實現的。《Effective Objective-C 2.0》中也專門用一小篇的內容來介紹NSCache的使用(第50條:構建緩存時選用NSCache而非NSDictionary),裏面有更精彩的內容。如果我們需要構建緩存機制,則應該使用NSCache,而不是NSDictionary,這樣可以減少我們應用對內存的佔用,從而達到優化內存的目標。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章