(相信大家对这个话题都不陌生,甚至比我更精通,我写这篇分享的主要目的是让自己巩固记忆,因为我曾经对此非常困惑。欢迎大家指正错误、查漏补缺。)
有限的内存空间,使得iOS设备的内存常常吃紧,这时系统会向运行中的应用发出低内存警告,从而调用UIApplicationDelegate的方法:
- (void)applicationDidReceiveMemoryWarning:(UIApplication*)application
实现这个代理方法时应该通过清除能够被重新创建或加载的缓存数据或对象来释放尽量多的内存。并且要结合UIViewController的didReceiveMemoryWarning方法和UIApplicationDidReceiveMemoryWarningNotification通知来使用。强烈建议你实现这个方法。如果你的应用在低内存条件下不释放足够多的内存,系统可能会彻底终止你的应用。
所以,如何正确实现这两个方法是保证应用在低内存环境下能够继续正常使用的关键。
在iOS4和iOS5系统中,当内存不足,应用收到MemoryWarning时,系统会自动调用当前未显示在界面上的ViewController的viewDidUnload方法。通常情况下,这些未显示在界面上的ViewController是UINavigationController Push栈中未在栈顶的ViewController,以及UITabBarViewController中未显示的子ViewController。这些ViewController都会在MemoryWarning事件发生时,被系统自动调用viewDidUnload方法。
苹果官方文档提到:
需要注意的是,如果要使ViewController重新显示时,界面正常恢复,那就要保证需要显示的控件或者view对象不适用xib创建,而要在loadView或者viewDidLoad方法中创建,否则界面元素会缺失。因为ViewController在初始化时,才会从xib中创建对象。
从上图可以看出,自iOS6.0开始viewDidUnload方法已被取缔,那么,原本在viewDidUnload中的代码应该怎么处理?在iOS6中,又应该怎么处理内存警告?
对此,苹果在文档中建议,应该将回收内存的相关操作移到另一个回调函数didReceiveMemoryWarning 中。但是如果你仅仅是把以前写到viewDidUnload函数中的代码移动到didReceiveMemoryWarning函数中,那么你就错了。以下是一个错误的示例代码:
-(void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if ([self isViewLoaded] && ![[selfview] window]) {
[self setView:nil];
}
}
iOS6不推荐你将view置为nil,原因如下:
1、UIView有一个CALayer的成员变量,CALayer是具体用于将自己画到屏幕上的。如下图所示:
2、CALayer是一个bitmap图象的容器类,当UIView调用自身的drawRect方法时,CALayer才会创建这个bitmap图象类。
3、具体占内存的其实是一个bitmap图象类,CALayer只占48bytes,UIView只占96bytes。而一个iPad的全屏UIView的bitmap类会占到12M的大小!
4、在iOS6系统中,当系统发出MemoryWarning时,系统会自动回收bitmap类,但是不回收UIView和CALayer类。这样既回收了大部分内存,又能在需要bitmap类时,通过调用UIView的drawRect方法重建。
还有另外一个小技巧:当一段内存被分配时,它会被标记成“In use”, 以防止被重复使用。当内存被释放时,这段内存会被标记成“Not in use”。这样,在有新的内存申请时,这块内存就可能被分配给其它变量。
CALayer包括的具体bitmap内容的私有成员变量类型为CABackingStore,当收到MemoryWarning时,CABackingStore类型的内存区会被标记成volatile类型(这里的volatile和C以及Java语言的volatile不是一个意思),volatile表示这块内存可能被再次被原变量重用。
这样,有了上面的优化后,当收到MemoryWarning时,虽然所有的CALayer所包含的bitmap内存都被标记成volatile了,但是只要这块内存没有再次被复用,那么当需要重建bitmap内存时,它就可以直接被复用,而避免了再次调用 UIView的drawRect方法。
简单来说,对于iOS6,你不需要做任何以前viewDidUnload的事情,更不需要把以前viewDidUnload的代码移动到didReceiveMemoryWarning方法中。
使用真机调试程序时,比较难复现低内存环境,我们可以使用模拟器来模拟低内存警告,操作步骤如下图所示: