1 NSCache
和 NSDictionary
的区别
首先看一下 Apple
对 NSCache
的介绍:
* 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
在存储时,调用了NSCopying
的copyWithZone
方法。对代码进行如下调整:
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
使用后,其内存地址发生了变化。从 v
的retainCount
属性变化,能够看出无论是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
的自动清除是不正确的,而是其数据内容的计数可以配合beginContentAccess
和endContentAccess
进行加/减,在其为 0 时,该对象可以使用discardContentIfPossible
进行内存的及时回收。
4 内存警告时的处理。
收到内存警告时,可以使用手动移除NSCache
和NSPurgeableData
对象,及时释放占用的内存。
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
[self.cache removeAllObjects];
//内存清除后,在 cache 属性 evictsObjectsWithDiscardedContent 为 YES(默认)时,会自动从NSCache中移除
[self.data discardContentIfPossible];
}
其中NSCache
中存储的NSPurgeableData
中内容被discard
成功时,同时NSCache
的evictsObjectsWithDiscardedContent
属性为YES
时(系统默认为YES),会自动将NSPurgeableData
移除。
5 NSCache
和NSMutableDictinary
作为缓存时的性能对比
这里贴一下某个前辈的两个总结:
性能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
网友评论