构建缓存时选用NSCache而非NSDictionary
在写 程序时,经常遇到从网络上下载图片的问题,比如客户要求应用上的图片他们可以在后台修改等,那从网上下载的图片如何来缓存呢?
一开始我们的做法是将图片存到一个NSMutableDictionary中,这样稍后使用时就无需下载了,但是Foundation还给我们提供了一个NSCache的类,从名字我们就知道是用来处理缓存的。
问题一:为什么使用NSCache?
NSCache的好处在于当系统资源耗尽时,它可以自动删减缓存;如果采用NSDictionary,那么就要自己编写程序在收到系统发出的“低内存”通知时手动删减缓存。那么都能实现要求为什么还要使用NSCache呢?
(1) 首先NSCache是资源耗尽时自动删减缓存,比起NSDictionary来说简单;
(2)它是Foundation框架的一部分,所以它能在更深层次上处理效率会更好;
(3)NSCache会先行删减“最久未使用”的对象,若自己为字典添加此功能会很复杂。
(4) NSCache是线程安全的,再不编写锁代码时,多个线程可以同时访问NSCache
(5)NSCache不会拷贝键 ,而是强引用键,因为键大部分是由不支持拷贝的对象来充当。
问题二:NSCache在系统资源耗尽是,会自动删减内存,那我可以控制什么时候删减内存吗?
我们可以使用NSCache的两个与系统资源相关的尺寸来操控缓存删减起内容的时机。
(1)缓存可就收的对象总数即countLimit属性,
(2)所有对象的“总开销”即totalCostLimit属性,我们在将对象加入缓存时,可以指定“”开销值“。当对象或总开销超过上限时,缓存可能会删减其中的对象,也可能在系统资源紧缺时删减
注意:可能删减,意味着不一定会删除,所以想通过调整开销值迫使缓存删减对象的情况下,不应使用NSCache;
问题三:我们在任何时候都使用NSCache就好了?
不对,使用任何方案都要了解它的利弊,对于“开销值”这个控制删减内容的尺寸的使用有一些注意事项,在向内存添加对象时,应该很容易计算出它的“开销值”(也就是大小),才应该是用这个尺度,例如加入缓存的事NSData对象,可以将它的大小当作“开销值”,因为不必计算,读取NSData的大小就可以了。例如必须访问磁盘才能确定文件的大小,或是必须访问数据库才能决定具体取值就不应使用“开销值”。
stackoverflow上的说明:如果可以在运行时重新创建的值(通过从Internet下载,通过计算,无论如何),NSCache可以满足您的需要。 如果无法重新创建数据(例如用户输入,时间敏感等),则不应将其存储在NSCache中,因为它将在那里被销毁。
问题四:怎样使用它?
(1)基本了解和使用
我们先介绍一个与它配合使用的NSPurgeableData,NSPurgeableData是NSMutableData类的子类,实现了NSDiscardableContent协议。当系统资源紧张时,可以把保存为NSPurgeableData对象的内存释放掉。
需要访问NSPurgeable对象,可调用beginContentAccess方法,表示不应丢弃自己占的内存。用完之后,调用endContentAccess方法,表示在必要时可以丢弃自己占的内存。记住只有对象引用计数为0时才可以丢弃。
因为缓存中NSPurgeableData对象在被系统丢弃时,会自动从缓存中移除,NSCache的evictsObjectsWithDiscardedContent属性用于标志是否开启此功能。
stackoverflow上的介绍
// Your cache should have a lifetime beyond the method or handful of methods
// that use it. For example, you could make it a field of your application
// delegate, or of your view controller, or something like that. Up to you.
NSCache *myCache = ...;
NSAssert(myCache != nil, @"cache object is missing");
// 在cache外尝试获取cache中的对象
Widget *myWidget = [myCache objectForKey: @"Important Widget"];
if (!myWidget) {
//如果cache中没有或被移除了,我们必须创建它;
//可以推测创建是个耗资源的操作(这是为什么我们使用cache),如果不耗资源我们可能不需要cache
myWidget = [[[Widget alloc] initExpensively] autorelease];
//把它放进缓存,他可能永久存在,也可能在任何时候被移除,
//那样我们必须再下一次使用时创建它
[myCache setObject: myWidget forKey: @"Important Widget"];
}
// myWidget 存在,使用它
myWidget = [myCache objectForKey:@“Important Widget"”];
if (myWidget) {
[myWidget runOrWhatever];
}
总结:
实现缓存时应选用NSCache而非NSDictionary对象。因为NSCache可以提供优雅的自动删减功能,而且是线程安全的,此外,它与字典不同,并不会拷贝键。
可以给NSCache对象设置上限,用以限制缓存中的对象总个数及总成本,而这些尺度则定义了缓存删减其中对象的时机。但是绝对不要把这些尺度当成可靠的硬限制,他们仅对NSCache起指导作用。
将NSPurgeableData与NSCache搭配使用,可实现自动清除数据的功能,也就是说,当NSPurgeableData对象所占内存为系统丢弃时,该对象自身也会从缓存中移除。
如果缓存使用得当。那么应用程序的响应速度就能提高。只有那种重新计算起来很费事的数据,才值得放入缓存,比如那些需要从网络获取或从磁盘读取的数据。
实例:在使用大量图片的app中,需要从存储里面读取数据,每次都从文件系统里面读取文件会造成卡顿现象。
解决办法就是把NSData对象缓存起来,先从NSCache里面读取数据,然后再从文件系统获取数据,提高效率。通过这样的方式,形成了 内存 -> 文件系统 -> 网络图片 的三级图片访问系统。
问题五:我想知道是否最好使用一个NSCache的全局实例,或者是每个需要它的组件的几个实例。
例如,我有几个视图子类将内容绘制到图像中并缓存它们,以避免一直重新生成它们。 每个类都有一个NSCache实例,还是整个应用程序只有一个集中的NSCache?
请注意,我不是每个实例的一个缓存一个缓存。 我在说每个类的NSCache的一个实例。
回答1:我通常会为每种类型的缓存对象投一个缓存。 这样,您可以为每个例如不同的countLimit值。 要指定“保持最近呈现的50个缩略图”,“保留最近下载的5个最大的图像”,“保留最近下载的10个PDF”等。
对于真正计算上昂贵的任务,我还采用两层缓存,NSCache来实现最佳性能,并将其保存到本地文件系统中的临时/缓存目录中,以避免昂贵的下载周期,同时不消耗RAM。
回答2:如果缓存的内容在组件之间共享,那么可以使用统一的缓存 - 查找不会显着增加,至少可以减少缓存对象的冗余副本。
但是,如果缓存的内容对于每个组件是唯一的,则将它们混合在高速缓存中是无意义的,并且从代码可读性的角度来看可能会令人困惑。 在这种情况下保持缓存分开。 这也让您更精确地控制它们 - 例如 您可以更积极地从不被立即使用的组件中缓存。
总结:如果缓存的内容在组件间共享可以创建一个统一的缓存,如果不是组件间共享建议为每种类型的内容单独创建缓存,这样我们可以细粒度的控制他们,分别设置他们的countLimit等;而且我们可以设计两级缓存
网友评论