聊聊NSCache

打算了解一下NSCache可能要從前一段時間面試講起,當(dāng)時面試者問我了解NSCache嗎?我的第一印象是:這是什么類,怎么從來沒聽過,難道他說的是NSURLCache?于是跟他扯了一通h5的離線緩存的實現(xiàn)。面試完回來一查直接傻眼了,因此做一次學(xué)習(xí)記錄吧。

1.NSCache簡述

An NSCache object is a mutable collection that stores key-value pairs, similar to an NSDictionary object. The NSCache class provides a programmatic interface to adding and removing objects and setting eviction policies based on the total cost and number of objects in the cache.

  • The NSCache class incorporates various auto-eviction policies, which ensure that a cache doesn’t use too much of the system’s memory. If memory is needed by other applications, these policies remove some items from the cache, minimizing its memory footprint.
  • You can add, remove, and query items in the cache from different threads without having to lock the cache yourself.
  • Unlike an NSMutableDictionary object, a cache does not copy the key objects that are put into it.
  • NSCache是一個類似NSDictionary一個可變的集合。
  • 提供了可設(shè)置緩存的數(shù)目與內(nèi)存大小限制的方式。
  • 保證了處理的數(shù)據(jù)的線程安全性。
  • 緩存使用的key不需要是實現(xiàn)NSCopying的類。
  • 當(dāng)內(nèi)存警告時內(nèi)部自動清理部分緩存數(shù)據(jù)。

2.NSCache使用

NSCache *cache = [[NSCache alloc] init];
cache.delegate = self;
//cache.countLimit = 50; // 設(shè)置緩存數(shù)據(jù)的數(shù)目
//cache.totalCostLimit = 5 * 1024 * 1024; // 設(shè)置緩存的數(shù)據(jù)占用內(nèi)存大小
    
- (void)start:(id)sender{
    
    for(int i = 0;i < 1000;i++){
        NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"pptx"]];
        
        // 1.緩存數(shù)據(jù)
        [cache setObject:data forKey:[NSString stringWithFormat:@"image_%d",arc4random()]];
    }
}

#pragma mark - NSCacheDelegate
- (void)cache:(NSCache *)cache willEvictObject:(id)obj{
    NSLog(@"刪除緩存數(shù)據(jù)");
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    NSLog(@"內(nèi)存警告");
}

執(zhí)行結(jié)果:

2017-06-18 22:51:58.204455 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:51:58.204812 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:51:58.205198 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:51:58.205521 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:51:58.205918 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:51:58.206216 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:52:05.207987 InterView[1113:223367] 內(nèi)存警告

3.NSCache的使用場景

3.1 AFNetworking(2.X)中UIImageView+AFNetworking的圖片緩存

// 緩存的key使用請求的路徑
static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) {
    return [[request URL] absoluteString];
}


// 繼承NSCache,實現(xiàn)自定義的cache策略
@interface AFImageCache : NSCache <AFImageCache>
@end

@implementation AFImageCache

- (UIImage *)cachedImageForRequest:(NSURLRequest *)request {
    switch ([request cachePolicy]) {
        case NSURLRequestReloadIgnoringCacheData:
        case NSURLRequestReloadIgnoringLocalAndRemoteCacheData:
            return nil;
        default:
            break;
    }

    return [self objectForKey:AFImageCacheKeyFromURLRequest(request)];
}

- (void)cacheImage:(UIImage *)image
        forRequest:(NSURLRequest *)request
{
    if (image && request) {
        [self setObject:image forKey:AFImageCacheKeyFromURLRequest(request)];
    }
}
@end
AFImageCache具體的使用
+ (id <AFImageCache>)sharedImageCache {
    static AFImageCache *_af_defaultImageCache = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _af_defaultImageCache = [[AFImageCache alloc] init];
        // 收到內(nèi)存警告直接清理掉緩存
        [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * __unused notification) {
            [_af_defaultImageCache removeAllObjects];
        }];
    });

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: _af_defaultImageCache;
#pragma clang diagnostic pop
}

AF 3.0及以上已經(jīng)替換了實現(xiàn)的方式(NSMutableDictionary + GCD保證線程安全),有興趣可以直接自己看一下3.0源碼。

3.2 SDWebImage中SDImageCache圖片緩存

// 繼承NSCache,實現(xiàn)自定義的cache類
@interface AutoPurgeCache : NSCache
@end

@implementation AutoPurgeCache

- (id)init
{
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];

}

@end

AutoPurgeCache的使用

初始化
// Init the memory cache
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace;
緩存圖片與取緩存圖片
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
    return [self.memCache objectForKey:key];
}

- (UIImage *)imageFromDiskCacheForKey:(NSString *)key {

    // First check the in-memory cache...
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        return image;
    }

    // Second check the disk cache...
    UIImage *diskImage = [self diskImageForKey:key];
    if (diskImage && self.shouldCacheImagesInMemory) {
    
        // 計算需要緩存的內(nèi)存空間
        NSUInteger cost = SDCacheCostForImage(diskImage);
        [self.memCache setObject:diskImage forKey:key cost:cost];
    }

    return diskImage;
}

3.3 React Native(0.38)

  • RCTAsyncLocalStorage數(shù)據(jù)緩存類
  • RCTImageCache圖片緩存類

二者都使用到NSCache完成數(shù)據(jù)的緩存,初始化與使用與上述的AFNetworkingSDWebImage都很類似,基本原理相同此處不做贅述了。

4.遇到問題

NSCachetotalCostLimit設(shè)置了為什么沒有生效?

demo中的例子我把cache.totalCostLimit = 5 * 1024 * 1024;注釋打開,執(zhí)行發(fā)現(xiàn)直到內(nèi)存警告才開始自動清理數(shù)據(jù)?嘗試了很多次都是一樣的結(jié)果。那設(shè)置的5M的最大的緩存大小為什么沒有起到作用呢?重新查看一下蘋果的文檔關(guān)于totalCostLimit的描述:

Discussion:

If 0, there is no total cost limit. The default value is 0.
When you add an object to the cache, you may pass in a specified cost for the object, such as the size in bytes of the object. If adding this object to the cache causes the cache’s total cost to rise above totalCostLimit, the cache may automatically evict objects until its total cost falls below totalCostLimit. The order in which the cache evicts objects is not guaranteed.
This is not a strict limit, and if the cache goes over the limit, an object in the cache could be evicted instantly, at a later point in time, or possibly never, all depending on the implementation details of the cache.

注意加粗部分,是需要使用如下的接口嗎?

- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;

動手嘗試將demo中的setObject換成如下實現(xiàn),發(fā)現(xiàn)執(zhí)行一次就已經(jīng)觸發(fā)了自動清理緩存的回調(diào),也基本驗證了這一點。

 NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"pptx"]];
[cache setObject:data forKey:[NSString stringWithFormat:@"image_%d",arc4random()] cost:10 * 1024 * 1024];

回頭查看AFNetworking以及SDWebImage以及RN中的兩處緩存的使用,也充分印證了這一點:設(shè)置全局緩存實例時如果設(shè)置了totalCostLimit必然存儲緩存的方法調(diào)用必然帶上了cost,否則totalCostLimit是無用的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 起落之間 過去的一周,是我生活、思緒、情緒無比混亂的一周,好在我始終在用自我療愈,好在還沒有到需要心理醫(yī)生的地步,...
    像拉拉一樣奮斗閱讀 94評論 0 0
  • 第一幅自己獨立完成的作品,還知道不留白,可以!
    豬豬家的小狗狗妹閱讀 171評論 0 1
  • 今天這節(jié)數(shù)學(xué)課,我覺得上得很成功。班上所有學(xué)生都能跟著我的節(jié)奏一起來,包括平時壓根兒不怎么聽課的周康、劉家旺這節(jié)課...
    我與你的對白閱讀 835評論 0 1
  • 不好,又要遲到了,上學(xué)第一天不能遲到??!上學(xué)第一天不能遲到??!要是遲到了又要被老爸挨批了! ...
    美絮閱讀 208評論 0 0

友情鏈接更多精彩內(nèi)容