美文网首页
NSCache 的理解

NSCache 的理解

作者: uniapp | 来源:发表于2020-06-16 11:53 被阅读0次
1 NSCacheNSDictionary 的区别

首先看一下 AppleNSCache 的介绍:

* 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.

简单翻译一下:
(1) NSCache 内部含有自动丢弃策略,保证不占用过多内存;
(2) 线程安全;
(3) 和 NSMutableDictionary 不同,存储时不会对 key 进行copy
NSCache的 3 个特点可以这样理解:内部的机制,保证了存入的缓存数据能够及时释放;内部含锁,保证线程安全,相比 NSDictionary, 执行效率会降低;对 key 不拷贝,能带来执行效率的提升。

对特点 3 举例验证:

UIView *v = [UIView new];
NSMutableArray *array = [NSMutableArray array];

[self.cache setObject:array forKey:v];    
[self.dict setObject:array forKey:v];

NSMutableArray类实现了NSCopying协议, 而UIView 类没有。上述代码在执行到最后一行时,会报错:

reason: '-[UIView copyWithZone:]: unrecognized selector sent to instance 0x7fe29fe097c0'

说明 NSDictionary 在存储时,调用了NSCopyingcopyWithZone方法。对代码进行如下调整:

UIView *v = [UIView new];
NSMutableArray *array = [NSMutableArray array];

[self.cache setObject:v forKey:array];
[self.dict setObject:v forKey:array];

执行成功。在控制台查询更详细信息:

(lldb) po [v valueForKey:@"retainCount"]
2
(lldb) po [v valueForKey:@"retainCount"]
3
(lldb) po array
<__NSArrayM 0x600002d223a0>(
)
(lldb) po [self.dict allKeys]
<__NSSingleObjectArrayI 0x600002170370>(
<__NSArray0 0x7fff8062d430>(
)

能够看到NSArray 作为 NSDictionary 使用后,其内存地址发生了变化。从 vretainCount属性变化,能够看出无论是NSCache, 还是NSDictionary,存储时,都是将value值的retainCount增加,也就是对值的浅copy

2 NSCache 的使用

使用NSCache作为容器使用,在其delegate方法- (void)cache:(NSCache *)cache willEvictObject:(id)obj中,能够看到其移除内部数据的过程。

NSCache设置足够的个数和容量,反复 3 次读取图片数据在NSCache中存储,然后将 App 退到后台。

- (NSCache *)cache {
    if (!_cache) {
        _cache = [NSCache new];
        _cache.countLimit = 10;
//byte计算
        _cache.totalCostLimit = 100000;
        _cache.delegate = self;
        _cache.evictsObjectsWithDiscardedContent = YES;
    }
    return _cache;
}

#pragma mark - NSCacheDelegate
- (void)cache:(NSCache *)cache willEvictObject:(id)obj {
    NSLog(@"删除数据:%@", obj);
}

- (void)btnClick {
    for(int i = 0;i < 3;i++){
        NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle]
    pathForResource:@"icon_big.png" ofType:nil]];
        [self.cache setObject:data forKey:[NSString stringWithFormat:@"data_%d",i] cost:0];
        NSLog(@"存入数据%d:%@", i, data);
    }
}

观察控制台输出数据:

2020-06-16 10:22:29.213051+0800 NSCacheTest[70324:1233069] 存入数据0:{length = 19050, bytes = 0x89504e47 0d0a1a0a 00000004 43674249 ... 49454e44 ae426082 }
2020-06-16 10:22:29.213375+0800 NSCacheTest[70324:1233069] 存入数据1:{length = 19050, bytes = 0x89504e47 0d0a1a0a 00000004 43674249 ... 49454e44 ae426082 }
2020-06-16 10:22:29.213715+0800 NSCacheTest[70324:1233069] 存入数据2:{length = 19050, bytes = 0x89504e47 0d0a1a0a 00000004 43674249 ... 49454e44 ae426082 }
2020-06-16 10:22:33.587077+0800 NSCacheTest[70324:1233069] 删除数据:{length = 19050, bytes = 0x89504e47 0d0a1a0a 00000004 43674249 ... 49454e44 ae426082 }
2020-06-16 10:22:33.587236+0800 NSCacheTest[70324:1233069] 删除数据:{length = 19050, bytes = 0x89504e47 0d0a1a0a 00000004 43674249 ... 49454e44 ae426082 }
2020-06-16 10:22:33.587356+0800 NSCacheTest[70324:1233069] 删除数据:{length = 19050, bytes = 0x89504e47 0d0a1a0a 00000004 43674249 ... 49454e44 ae426082 }

能够看出App进入后台时,NSCache主动释放内部全部数据

3 NSPurgeableData

查找网上NSCache资料时,发现很多均连带了NSPurgeableData。查询NSPurgeableData的文档介绍中,有这么一段:

You may use these objects by themselves, and do not necessarily have 
to use them in conjunction with NSCache to get the purging behavior. 
The NSCache class incorporates a caching mechanism with some auto-removal 
policies to ensure that its memory footprint does not get too large.

大意是说NSPurgeableData可以独立NSCache使用,NSCache内部有一些自动清理机制,保证不占用太大内存。对这么多NSCache文章中连带介绍NSPurgeableData`原因,感到有些疑惑。

将上述存入NSCache中的数据,替换成NSPurgeableData, 然后让app进入后台。

for(int i = 0;i < 3;i++){
    NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"icon_big.png" ofType:nil]];
    NSPurgeableData *data2 = [NSPurgeableData dataWithData: data];
    [self.cache setObject:data2 forKey:[NSString stringWithFormat:@"image_%d",i] cost:data2.length];
    [data2 endContentAccess];
    NSLog(@"存入数据%d:%@", i, data);
}

2020-06-16 10:27:00.632593+0800 NSCacheTest[70567:1239254] 存入数据0:{length = 19050, bytes = 0x89504e47 0d0a1a0a 00000004 43674249 ... 49454e44 ae426082 }
2020-06-16 10:27:00.632939+0800 NSCacheTest[70567:1239254] 存入数据1:{length = 19050, bytes = 0x89504e47 0d0a1a0a 00000004 43674249 ... 49454e44 ae426082 }
2020-06-16 10:27:00.633179+0800 NSCacheTest[70567:1239254] 存入数据2:{length = 19050, bytes = 0x89504e47 0d0a1a0a 00000004 43674249 ... 49454e44 ae426082 }

从控制台的结果中,发现NSCache并没有自动清除NSPurgeableData的过程。个人理解NSPurgeableData的自动清除是不正确的,而是其数据内容的计数可以配合beginContentAccessendContentAccess进行加/减,在其为 0 时,该对象可以使用discardContentIfPossible进行内存的及时回收。

4 内存警告时的处理。

收到内存警告时,可以使用手动移除NSCacheNSPurgeableData对象,及时释放占用的内存。

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    
    [self.cache removeAllObjects];
    //内存清除后,在 cache 属性 evictsObjectsWithDiscardedContent 为 YES(默认)时,会自动从NSCache中移除
    [self.data discardContentIfPossible];
}

其中NSCache中存储的NSPurgeableData中内容被discard成功时,同时NSCacheevictsObjectsWithDiscardedContent属性为YES时(系统默认为YES),会自动将NSPurgeableData移除。

5 NSCacheNSMutableDictinary作为缓存时的性能对比

这里贴一下某个前辈的两个总结:

性能1
性能2
总结

相比NSDictionary,使用 NSCache 作为缓存能够将占用的内存及时回收,进入后台时会主动将内部存储的数据进行释放,而且在满足条件时,会自动移除NSPurgeableData类型的数据。NSCache并非完美的缓存,因为线程安全的存在,效率欠佳。

参考:
1 https://juejin.im/entry/5948bd53fe88c2006a93744e
2 https://juejin.im/post/5d85dfe7e51d4561f777e28d
3 http://blog.ipalfish.com/?author=22

喜欢和关注都是对我的鼓励和支持~

相关文章

网友评论

      本文标题:NSCache 的理解

      本文链接:https://www.haomeiwen.com/subject/emylxktx.html