Caching和Purgeable Memory对于开发者来说是一个至关重要的资源,尤其是当我们需要处理那些需要超大内存以及计算时间的对象或者是当计算机向磁盘写入数据时导致应用程序陷入停滞时特别有用处。
一、Caching概述
Caching是一个可以显著提高应用程序性能的对象或者是数据的集合。
1、为什么使用 Caching
开发者可以使用caches来存储那些需要频繁访问的对象并且这些对象中包含了需要大量CPU时间计算得来数据。由于这些值不需要再重复计算,重用这些数据对象有助于提供我们的应用程序性能。然而,对于应用程序来说,这些对象并不是至关重要的,并且可以在内存吃紧的时候释放掉。如果这些对象被释放,那么它包含的值就会在需要的时候重新计算。
2、 Caching可能引发的问题
尽管从性能的角度上来讲,caching可以带来巨大的好处,但是它同样也有一些缺点。最重要的是,缓存会使用大量的内存空间。当我们缓存了大量的大数据对象时,可能会导致没有足够的内存供其它应用程序使用,这个时候计算机可能会启动页面置换(OSX)或者是通知其它应用程序释放内存(IOS)以此来获取内存空间。
3、解决方案
Cocoa 框架提供了一个NSCache对象来管理我们任何需要cache的对象,它是一个对象容器。NSCache对象类和NSDictionary对象类非常相似,它们都拥有键值对。不同的是,NSCache对象是一个"活性的Cache",也就是说,当我们的内存足够用时,他会缓存足够多的对象。当内存不够时,它会自动清理其中的某些对象来释放内存空间供其它应用程序使用。然后,如果我们需要再次使用这些销毁的对象,这些对象的值会重新计算。
NSCache提供了两个有用的限制特性:限制缓存项的数目和限制缓存项总的内存消耗。通过调用 setCountLimit:
可以限制缓存项的数目。例如,如果我们尝试将第11个对象缓存到一个只有10个缓存项数量的NSCache中,NSCache可能会自动销毁其中的一个对象。
当我们向缓存中添加缓存项时,我们可以通过 setTotalCostLimit:
指定一个cost值给这个缓存,来设置最大可缓存的所有对象的内存大小是多少。当缓存超过这个限制时,他可能会自动销毁其中的某些对象让这个缓存值低于最大的内存大小限制。这个自动销毁处理机制并不能保证性能,尝试操作这个值有时候会产生性能上的开销,可能获取某一个资源的大小的开销还不足以抵消使用缓存带来的好处。当然,如果你缓存的数据没有什么大的用处,传入0就行了,当然你也可以通过 setObject:forKey:
这种KVC的方式来设置这个值。传0并不会带来任何开销。
缓存项数目和缓存总量并不是绝对的指标,也就是说可用系统资源趋于紧张时,可能会删减某个对象不代表一定会删减某个对象。删减对象所遵照的顺序有具体的实现而定。这尤其说明:想通过调整开销值来迫使缓存优先删减某些对象并不是一个好主意。
二、使用Purgeable Memory
Cocoa框架提供了NSPurgeableData对象类来帮助应用程序不会使用过大的内存空间。NSPurgeableData遵守NSDiscardableContent协议,任何实现了该协议的对象类都可以在其它对象使用完毕该对象实例后允许内存被释放。当我们创建了那些拥有很多可随意处理的子控件的对象的时候,我们应该实现这个协议。另外,NSPurgeableData对象没有必要一定要与NSCache对象类配合使用。我们只需要使用它一个就能获取到Purgeable特性。
1、使用Purgeable Memory的优势
通过使用purgeable memory,我们可以在系统需要时快速的恢复内存,因此可以提高性能。因为页面置换是一个十分耗时的操作,所以当那些被标记为purgeable的内存被虚拟内存系统(VMS)回收时不会被页换出到磁盘。取而代之的是,这些对象占用的内存数据会被销毁,如果之后需要用到时会再次计算即可。(Purgeable Memory占用的内存不会因为内存不足置换到backing store,仅仅只会销毁,)
需要注意的时,如果我们使用purgeable memory,它所占用的那部分内存在使用之前会被锁定。这个锁定机制是十分有必要的,它确保了不会因为自动回收机制释放了那些你想要在之后访问的内存数据。同样,这个锁定机制页确保了虚拟内存系统没有销毁这部分数据。NSPurgeableData
对象类通过这样一个简单的锁定机制来确保数据在读取之时时安全的。
2、如何实现Purgeable Memory
NSPurgeableData
对象类的使用非常简单,这个对象类仅仅实现了 NSDiscardableContent
协议。对于 NSDiscardableContent
对象的生命周期来说,核心思想就是引用计数。当一个对象使用一块内存时,这块内存区域的引用计数>=1。当它不在被使用并且可以被销毁时,它的引用计数=0。
当引用计数变为0时,在内存吃紧的情况下,这块内存就会被回收。为了释放这块内存区域,通过调用对象的l discardContentIfPossible
方法,我们可以释放这块引用计数为0的内存区域。
默认情况下,当一个NSPurgeableData
对象初始化的时候,它的引用计数变量值为1,并且可以安全的被访问。为了访问这个purgeable memory,仅仅调用 beginContentAccess
方法即可。这个方法首先会检查这个对象的数据是否被销毁。如果这个数据仍然在,它将会增加这个对象指向的内存的引用计数,并且返回YES。如果这个对象的数据已经被销毁了,这个方法就会返回NO。当我们完成对这个对象的访问后,调用endContentAccess
方法即可,这个方法会减少这块内存区域的引用计数,并允许内存在需要是释放它。只有当 beginContentAccess
方法返回YES时,我们才可以去访问这个对象所指向的内存空间。
当系统可用内存减少时,系统或客户端对象通过调用discardContentIfPossible
方法来销毁purgeable数据,当对象所指向的内存引用计数为0时,这个方法仅仅会销毁上面的数据,然后不再做其它任何操作。如果这个内存被销毁了,那么 isContentDiscarded
方法会返回YES。下面是一个使用NSPurgeableData
对象的例子:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 NSPurgeableData * data = [[NSPurgeableData alloc] init]; 2 3 [data endContentAccess]; //Don't necessarily need data right now, so mark as discardable. 4 5 //Maybe put data into a cache if you anticipate you will need it later. 6 ... 7 8 if([data beginContentAccess]) { //YES if data has not been discarded and counter variable has been incremented 9 10 ...Some operation on the data... 11 12 [data endContentAccess] //done with the data, so mark as discardable 13 14 } else { 15 16 //data has been discarded, so recreate data and complete operation 17 18 data = ... 19 20 [data endContentAccess]; //done with data 21 22 } 23 24 //data is able to be discarded at this point if memory is tight
3、Purgeable Memory和NSCache
当一个实现了 NSDiscardableContent
协议的对象放在NSCache对象中去时,cache对象就维持了一个对该对象的强引用。然后,如果这个对象的内容已经被销毁(discard)了,这个缓存的evictsObjectsWithDiscardedContent
的值将会被设置为YES,然后这个对象会自动从缓存中移除。
4、一些使用Purgeable Memory的警告
值得注意的是Purgeable memory是那些大容量对象才可以直接使用的内存。Purgeable API也是以多页大小虚拟内存对象来设计,这就使得我们很难把一个很小的缓存元素标记为purgeable。这个 cache API会做一些必须的记录以支持将让的缓存元素也可以使用purgeable memory。同样的,有时候通过API为这些缓存元素分配内存空间是不合适的,当我们可以十分方便的创建一个对象,或这个对象在不同层分配,并且这个层已经对它做了缓存。这种情况,不适合使用purgeable memory。
5、什么时候使用Purgeable Memory
当擦除这个对象的性能开销小于页面置换的性能开销时,我们可以考虑采用Purgeable Memory------页面置换的性能开销远大于再次使用这个对象时重新计算数据值的性能开销。很多时候缓存没没有遵循上面的规律,它们的一些缓存项很有可能以后都不会再次使用。同样的,那些能够轻松计算出数据的缓存项可以考虑使用purgeable memory,因为重新计算这些数据并不会耗费应用程序多少性能。
附:
总结:
1、使用Purgeable Memory可以让我们可以安全的使用某一个对象内存,可以通过方法判断该对象内存上的数据是否已经被擦除。
2、Purgeable Memory对应的内存不会被页换出到backing store,在内存不足时会被直接擦除,释放出的内存可供其它地方使用。
3.purgeable memory 定义可直接擦除,而不需要进行页面置换。ios中所有对象都是purgeable ,但是并不是所有对象都可以知晓自己使用的内存是否被擦除,这就必须借助实现了nsdiscardablecontent协议的对象来完成更精确的控制。
猜想:内核系统中维持了一个Purgeable 内存区域,里面维护了一些Purgeable Page列表,这种类型的Page也会与物理内存中的页有一个映射关系,当我们释放其中某个对象对应的内存页时,实际的物理内存区域会被系统回收供本应用程序或其它应用程序使用,但是Purgeable内存区域中也会维护一个列表来指示,这个对象的内存页当前已经被擦除。
实际用处:
1、可以使用 NSPurgeableData与NSData搭配使用,如果某个对象所占的内存能够根据需要随时丢弃,那么就可以实现该协议所定义的方法。也就是说当系统资源紧张时,可以把保存NSPurgeableData对象的那块内存释放掉。NSDiscardableContent协议里定义了名为isContentDiscarded方法,可以用来查询相关内存是否已经释放。
2、如果需要访问某个NSPurgeablData对象,可以调用其beginContentAccess方法,告诉系统现在还不应该丢弃自己所占用的内存。用完之后,调用endContentAccess方法,告诉系统在必要时可以丢弃自己所占据的内存。这些调用可以嵌套,所以说,它们就像递增与递减引用计数所用的方法一样。只有对象的引用计数为0时才可以丢弃。
3、如果将NSPurgeableData对象加入NSCache,在该对象被系统丢弃时也会自动从缓存中移除。通过NSCache的evictsObjectsWithDiscardedContent属性,可以开启或关闭此功能。(IOS系统中在发生内存不足会强制应用程序清理内存)。
4、实现缓存时应该选用NSCache而非NSDictionary对象。因为NSCache可以提供优雅的自动删减功能,而且还是线程安全的,此外它与字典不同,它并不会拷贝键。直接复制键引用。