構建緩存時選用 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 對象所占據的內存為系統所丟棄時, 該對象自身也會從緩存中移除.
如果緩存使用得當, 那么應用程序的響應速度就能提高, 只有那種 '重新計算起來很麻煩的'數據,才值得放入緩存, 比如那些需要從網絡獲取或 從 磁盤讀取的數據.