iOS内存警告

清空内存缓存,取消操作

IOS内存警告处理
IPhone下每个app可用的内存是被限制的,如果一个app使用的内存超过20M,则系统会向该app发送Memory Warning消息。收到此消息后,app必须正确处理,否则可能出错或者出现内存泄露。

app收到Memory Warning后会调用:

UIApplication::didReceiveMemoryWarning -> UIApplicationDelegate::applicationDidReceiveMemoryWarning,然后调用当前所有的viewController进行处理。因此处理的主要工作是在viewController。

我们知道,创建viewcontroller时,执行顺序是loadview -> viewDidLoad。

当收到内存警告时,如果viewcontroller未显示(在后台),会执行didReceiveMemoryWarning -> viewDidUnLoad;如果viewcontroller当前正在显示(在前台),则只执行didReceiveMemoryWarning。

当重新显示该viewController时,执行过viewDidUnLoad的viewcontroller(即原来在后台)会重新调用loadview -> viewDidLoad。

因此主要注意下面几个函数:

loadView

创建view,构建界面;

viewDidLoad

做些初始化工作。由于在初次创建viewcontroller和重新恢复时都会调用,因此这个函数需要注意区分不同的情况,设置正确的状态。

didReceiveMemoryWarning

释放不必须的内存,比如缓存,未显示的view等。

viewDidUnLoad

最大程度的释放可以释放的内存。比如应该释放view,这些view在调用loadview后可以重新生成。(其中成员变量释放后应设置为nil)。对于非界面的数据是否释放,需要具体分析,可以恢复的数据可以释放,不能恢复的数据就不要释放。

实际中如果viewcontroller是用xib生成的界面,则需要我们做的就比较少,主要是在viewDidLoad中恢复原来的界面状态。

如果是通过编程创建的界面,则需要做的工作就要更多些,上面4个函数中都需要进行正确处理。

iphone下每个app可用的内存是被限制的,如果一个app使用的内存超过20M,则系统会向该app发送Memory Warning消息。收到此消息后,app必须正确处理,否则可能出错或者出现内存泄露。
app收到Memory Warning后会调用:UIApplication::didReceiveMemoryWarning -> UIApplicationDelegate::applicationDidReceiveMemoryWarning,然后调用当前所有的viewController进行处理。因此处理的主要工作是在viewController。
当我们的程序在第一次收到内存不足警告时,应该释放一些不用的资源,以节省部分内存。否则,当内存不足情形依然存在,ios再次向我们程序发出内存不足的警告时,我们的程序将会被iOS kill掉。

iOS的UIViewController 类给我们提供了处理内存不足的接口。在iOS 3.0 之前,当系统的内存不足时,UIViewController的didReceiveMemoryWarining 方法会被调用,我们可以在didReceiveMemoryWarining 方法里释放掉部分暂时不用的资源。
从iOS3.0 开始,UIViewController增加了viewDidUnload方法。该方法和viewDIdLoad相配对。当系统内存不足时,首先UIViewController的didReceiveMemoryWarining 方法会被调用,而didReceiveMemoryWarining 会判断当前ViewController的view是否显示在window上,如果没有显示在window上,则didReceiveMemoryWarining 会自动将viewcontroller 的view以及其所有子view全部销毁,然后调用viewcontroller的viewdidunload方法。如果当前UIViewController的view显示在window上,则不销毁该viewcontroller的view,当然,viewDidunload也不会被调用了。但是到了ios6.0之后,这里又有所变化,ios6.0内存警告的viewDidUnload 被屏蔽,即又回到了ios3.0的时期的内存管理方式。

iOS3-iOS5.0以前版本收到内存警告:

调用didReceiveMemoryWarning内调用super的didReceiveMemoryWarning会将controller的view进行释放。所以我们不能将controller的view再次释放。
处理方法:

java代码
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];//如没有显示在window上,会自动将self.view释放。
// ios6.0以前,不用在此做处理,self.view释放之后,会调用下面的viewDidUnload函数,在viewDidUnload函数中做处理就可以了。
}
-(void)viewDidUnload
{
// Release any retained subviews of the main view.不包含self.view
//处理一些内存和资源问题。
10. [super viewDidUnload];
11.
12. }

iOS6.0及以上版本的内存警告:

调用didReceiveMemoryWarning内调用super的didReceiveMemoryWarning调只是释放controller的resouse,不会释放view
处理方法:
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];//即使没有显示在window上,也不会自动的将self.view释放。
// Add code to clean up any of your own resources that are no longer necessary.

        // 此处做兼容处理需要加上ios6.0的宏开关,保证是在6.0下使用的,6.0以前屏蔽以下代码,否则会在下面使用self.view时自动加载viewDidUnLoad
        if ([[UIDevice currentDevice].systemVersion floatValue] >= 6.0) {
         //需要注意的是self.isViewLoaded是必不可少的,其他方式访问视图会导致它加载 ,在WWDC视频也忽视这一点。
         if (self.isViewLoaded && !self.view.window)// 是否是正在使用的视图
         {
               // Add code to preserve data stored in the views that might be
               // needed later.

               // Add code to clean up other strong references to the view in
               // the view hierarchy.
               self.view = nil;// 目的是再次进入时能够重新加载调用viewDidLoad函数。
         }
       }
}

但是似乎这么写相对于以前并不省事。最终我们找到一篇文章,文章中说其实并不值得回收这部分的内存,原因如下:
1. UIView是UIResponder的子类,而UIResponder有一个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类时,根据CALayer类重建。
所以,iOS6这么做的意思是:我们根本没有必要为了几十byte而费力回收内存。

low-memory 处理思路
通常一个应用程序会包含多个view controllers,当从view跳转到另一个view时,之前的view只是不可见状态,并不会立即被清理掉,而是保存在内存中,以便下一次的快速显现。但是如果应用程序接收到系统发出的low-memory warning,我们就不得不把当前不可见状态下的views清理掉,腾出更多的可使用内存;当前可见的view controller也要合理释放掉一些缓存数据,图片资源和一些不是正在使用的资源,以避免应用程序崩溃。

思路是这样,具体的实施根据系统版本不同而略有差异,本文将详细说明一下iOS 5与iOS 6的low-memory处理。

iOS 5 的处理
在iOS 6 之前,如果应用程序接收到了low-memory警告,当前不可见的view controllers会接收到viewDidUnload消息(也可以理解为自动调用viewDidUnload方法),所以我们需要在 viewDidUnload 方法中释放掉所有 outlets ,以及可再次创建的资源。当前可见的view controller 通过didReceiveMemoryWarning 合理释放资源,具体见代码注释。

举一个简单的例子,有这样一个view controller:
@interface MyViewController : UIViewController {
NSArray *dataArray;
}
@property (nonatomic, strong) IBOutlet UITableView *tableView;
@end

对应的处理则为:

pragma mark -

pragma mark Memory management

  • (void)didReceiveMemoryWarning {
    // Releases the view if it doesn’t have a superview.
    [super didReceiveMemoryWarning];

    // Relinquish ownership any cached data, images, etc that aren’t in use.
    }

  • (void)viewDidUnload {
    // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
    // For example: self.myOutlet = nil;
    self.tableView = nil;
    dataArray = nil;

    [super viewDidUnload];
    }

iOS 6 的处理
iOS 6 废弃了viewDidUnload方法,这就意味着一切需要我们自己在didReceiveMemoryWarning中操作。
具体应该怎么做呢?

1.将 outlets 置为 weak
当view dealloc时,没有人握着任何一个指向subviews的强引用,那么subviews实例变量将会自动置空。
@property (nonatomic, weak) IBOutlet UITableView *tableView;

2.在didReceiveMemoryWarning中将缓存数据置空

pragma mark -

pragma mark Memory management

  • (void)didReceiveMemoryWarning
    {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    dataArray = nil;
    }
    不要忘记一点,每当tableview reload 的时候,需要判断一下 dataArray ,若为空则重新创建。

兼容iOS 5 与 iOS 6
好吧,重点来了,倘若希望程序兼容iOS 5 与 iOS 6怎么办呢? 这里有一个小技巧,我们需要对didReceiveMemoryWarning 做一些手脚:

pragma mark -

pragma mark Memory management

  • (void)didReceiveMemoryWarning
    {
    [super didReceiveMemoryWarning];

    if ([self isViewLoaded] && self.view.window == nil) {
    self.view = nil;
    }

    dataArray = nil;
    }

判断一下view是否是window的一部分,如果不是,那么可以放心的将self.view 置为空,以换取更多可用内存。

这样会是什么现象呢?假如,从view controller A 跳转到 view controller B ,然后模拟low-memory警告,此时,view controller A 将会执行self.view = nil ; 当我们从 B 退回 A 时, A 会重新调用一次 viewDidLoad ,此时数据全部重新创建,简单兼容无压力~~

Note:
如果你好奇Apple为什么废弃viewDidUnload,可以看看Apple 的解释:
Apple deprecated viewDidUnload for a good reason. The memory savings from setting a few outlets to nil just weren’t worth it and added a lot of complexity for little benefit. For iOS 6+ apps, you can simply forget about view unloading and only implement didReceiveMemoryWarning if the view controller can let go of cached data that you can recreate on demand later.

再见,viewDidUnload方法

MAY 18TH, 2013

前言

我在去年的一篇文章 《iOS5 中 UIViewController 的新方法》 中介绍了 iOS5 引入的关于 ViewController 的新方法。但是现在如果运行该文章中的 Sample 代码的话,你会发现 Log 中不会再出现 viewDidUnload 方法被调用的记录。这是因为在 iOS6 中,viewDidUnload 回调方法被 Deprecated 掉了。查看苹果的文档,可以看到如下的说明。

那么,原本在 viewDidUnload 中的代码应该怎么处理?在 iOS6 中,又应该怎么处理内存警告?带着这些问题,我查找了一些资料,在此分享给大家。

分析

在 iOS4 和 iOS5 系统中,当内存不足,应用收到 Memory warning 时,系统会自动调用当前没在界面上的 ViewController 的 viewDidUnload 方法。 通常情况下,这些未显示在界面上的 ViewController 是 UINavigationController Push 栈中未在栈顶的 ViewController,以及 UITabBarViewController 中未显示的子 ViewController。这些 View Controller 都会在 Memory Warning 事件发生时,被系统自动调用 viewDidUnload 方法。

在 iOS6 中,由于 viewDidUnload 事件在 iOS6 下任何情况都不会被触发,所以苹果在文档中建议,应该将回收内存的相关操作移到另一个回调函数:didReceiveMemoryWarning 中。但是如果你仅仅是把以前写到 viewDidUnload 函数中的代码移动到 didReceiveMemoryWarning 函数中,那么你就错了。以下是一个 错误的示例代码 :

1
2
3
4
5
6

  • (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    if([self isViewLoaded] && ![[self view] window]) {
    [self setView:nil];
    }
    }

这篇文章 解释了 iOS6 不推荐你将 view 置为 nil 的原因(链接打开需要翻墙), 翻译过来如下:

  • UIView 有一个 CALayer 的成员变量,CALayer 是具体用于将自己画到屏幕上的。如下图所示:

  • CALayer 是一个 bitmap 图象的容器类,当 UIView 调用自身的 drawRect 时,CALayer 才会创建这个 bitmap 图象类。

  • 具体占内存的其实是一个 bitmap 图象类,CALayer 只占 48bytes, UIView 只占 96bytes。而一个 iPad 的全屏 UIView 的 bitmap 类会占到 12M 的大小!

  • 在 iOS6 时,当系统发出 MemoryWarning 时,系统会自动回收 bitmap 类。但是不回收 UIView 和 CALayer 类。这样即回收了大部分内存,又能在需要 bitmap 类时,通过调用 UIView 的 drawRect: 方法重建。

内存优化

另外文章中还提到苹果的操作系统对此做的一个内存优化技巧,解释如下:

  • 当一段内存被分配时,它会被标记成 “In use“, 以防止被重复使用。当内存被释放时,这段内存会被标记成 “Not in use”,这样,在有新的内存申请时,这块内存就可能被分配给其它变量。

  • CALayer 包括的具体的 bitmap 内容的私有成员变量类型为 CABackingStore, 当收到 MemroyWarning 时, CABackingStore 类型的内存区会被标记成 volatile 类型(这里的 volatile 和 C 以及 Java 语言的 volatile 不是一个意思),volatile 表示,这块内存可能被再次被原变量重用。

这样,有了上面的优化后,当收到 Memoy Warning 时,虽然所有的 CALayer 所包含的 bitmap 内存都被标记成 volatile 了,但是只要这块内存没有再次被复用,那么当需要重建 bitmap 内存时, 它就可以直接被复用,而避免了再次调用 UIView 的 drawRect: 方法。

总结

所以,简单来说,对于 iOS6,你不需要做任何以前 viewDidUnload 的事情,更不需要把以前 viewDidUnload 的代码移动到 didReceiveMemoryWarning 方法中。

引用 WWDC 2012 中的一段话来给 viewDidUnload 说再见:

The method viewWillUnload and viewDidUnload. We’re not going to call them anymore. I mean, there’s kind of a cost-benifit equation and analysis that we went through. In the early days, there was a real performance need for us to ensure that on memory warnings we unloaded views. There was all kinds of graphics and backing stores and so forth that would also get unloaded. We now unload those independently of the view, so it isn’t that big of a deal for us for those to be unloaded, and there were so many bugs where there would be pointers into。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值