iPhone下每个App可用的内存是被限制的,如果一个App使用的内存超过20M,则系统会向该App发送Memory Warning
(内存警告)消息,收到此消息后,App必须正确处理,否则可能出错或出现内存泄漏。
目录
- 流程
- iOS 6以上版本的App对内存警告处理方法
- 相关方法
- loadView
- loadView
- viewDidLoad
- awakeFromNib
- initWithCoder
- 结论
官方文档:
重写didReceiveMemoryWarning
方法:
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
NSLog(@"didReceiveMemoryWarning");
}
流程
当应用可用内存过低导致系统发出内存警告的时候,便会触发didReceiveMemoryWarning
方法。App收到内存警告会调用:
UIApplication::didRecieveMemoryWarning -> UIApplicationDelegate::applicationDidRecieveMemoryWarning
然后调用当前所有的viewController
进行处理,因此处理的主要工作在viewController。
创建viewController时,执行顺序是loadView -> viewDidLoad
。
当收到内存警告时,didRecieveMemoryWarning会判断当前viewController的view是否显示在window
上:
- 如果viewController未显示(在后台),会执行
didRecieveMemoryWarning -> viewDidUnload
,前者会自动将viewController的view及其所有子view全部销毁 - 如果viewController当前正在显示(在前台),则只执行
didRecieveMemoryWarning
,viewController的view不会被销毁 - 当重新显示该viewController时,执行过
viewDidLoad
的viewController(即原来在后台)会重新调用loadView -> viewDidLoad
。
iOS 6以上版本的App对内存警告处理方法
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; //super调用的此方法即使没有显示在window上(在后台),也不会自动的将self.view释放。
//此处做兼容处理需要加上iOS 6.0的宏开关,保证是在6.0下使用的,6.0以前屏蔽以下代码,否则会在下面使用self.view时自动加载viewDidUnLoad
if ([[UIDevice currentDevice].systemVersion floatValue] >= 6.0) {
//需要注意的是self.isViewLoaded是必不可少的,其他方式访问视图会导致它加载 ,在WWDC视频也忽视这一点。
if (self.isViewLoaded && !self.view.window) { // 是否是正在使用的视图
//code
self.view = nil;// 目的是再次进入时能够重新加载调用viewDidLoad函数。
}
}
}
- iOS 6之前:
viewDidUnload
和didReceiveMemoryWarning
都会被调用。 - iOS 6之后:
viewDidUnload
不会被调用didReceiveMemoryWarning
依然被调用。系统会自动处理View相关的内存,我们不用担心。也就是说不再支持viewDidUnload
了。
官方文档的解释是:系统会自动控制大的View所占用的内存,其他小的View所占用的内存是极其微小的,不值得为了省内存而去清理然后在重新创建。 如果你需要在内存警告的时候释放业务数据或者做些其他的特定处理,你可以实现didRecieveMemory
方法。
苹果官方给出的相关解释方案总是美好的,但现实往往是残酷的:
- 我们的工程是ARC的。
- 我们会在viewController里面强持有(strong)大量子View得成员变量
- 我们实现了大量的viewDidUnload函数来释放(2)里面持有的那个子View
让我们看看我们的代码到了iOS6以后会发生什么事情。因为所有的子View都是strong
持有的,这样会导致,即使系统内存警告导致了View的回收,他们也不会被真正的释放。于是乎,我们的程序可能就在后台被系统频繁的杀死。
栗子🌰:
一个App有三个tab(选项卡界面元素,比如“首页”、“通知”和“消息”的tabs):tabA、tabB、tabC(都从viewController继承,并且都实现了
didRecieveMemoryWarning
)。当程序启动时,默认显示tabA,这时,tabA的viewDidLoad
被调用,并且加载数据显示给用户,然后切换到tabB,B会重复A的加载过程。
这时系统产生了一个内存警告,tabA、tabB、tabC三个对象都会受到警告。
- tabA对象:因为它已经不在当前UI显示了,所以满足
[self.view window] == nil
,相关View被释放。- tabB对象:正在显示,所有didReceiveMemoryWarning什么也不会干。
- tabC对象:最悲惨,从来没有显示过,viewDidLoad从来没调用过,也没有显示过。然后有个self.view .这句的调用会导致一个结果,就是C对象的viewDidLoad会被调用一次,于是他的逻辑就是释放前先创建一次,然后再把自己释放,是不是很悲剧。(所以apple给的方案也不一定完美靠谱)。
iOS 6之后,应该做的:
- 不要把子View当成员变量来持有,使用
tag
来操作(其实不管在哪个版本最后都这么做)。 - 不需要实现
viewDidLoad
,由系统自己来控制相关的内存释放。 - 在需要的时候实现
didRecieveMemory
来释放一些业务数据减少内存的占用,不要操作UIView。
相关方法
loadView
loadView
viewDidLoad
awakeFromNib
initWithCoder
正在学习…loadView / viewDidLoad / awakeFromNib / initWithCoder 总结
结论
所以流程应该是这样:
(loadView/nib
文件)来加载view
到内存 ——>viewDidLoad
函数进一步初始化这些view ——>内存不足时,调用viewDidUnload
函数释放views —->当需要使用view时又回到第一步