低内存处理

来源:互联网 发布:淘宝网店售后服务简介 编辑:程序博客网 时间:2024/05/01 11:05

1.系统有四种内存警告,定义如下:

    typedef enum {

        OSMemoryNotificationLevelAny      = -1,

        OSMemoryNotificationLevelNormal   =  0,

        OSMemoryNotificationLevelWarning  =  1,

        OSMemoryNotificationLevelUrgent   =  2,

        OSMemoryNotificationLevelCritical =  3

    } OSMemoryNotificationLevel;

  但是这几种警告之间是怎么变化的,到达什么阈值时会从一个level跳到另外一个level,文章中没有提及。

  通常我们在程序中接收到最多的就是Memory warning level 1,这个时候就证明系统内存紧张了,我们的程序必须做出相应,释放不必要的内存。通常如果我们处理得当的话,内存警告就会到此停止,恢复正常状态。如果我们在接收到memory warning level 1以后坐视不理的话,系统可能还会再尝试通知几次level 1,如果还是不去处理的话,系统将会发出更高一级的内存警告 level 2,通常的结果就是我们的App被强制退出,系统收回内存。当然系统在发出level 2的警告时,也会去尝试做一些清理内存的事,例如关闭不必要的后台程序等。很显然我们不该寄希望于系统自己清理,这就好比你的胳膊被划破了,身体肯定会有自修复功能,但是如果伤口比较大的话,肯定还是需要人工处理一下的。

  到目前位置我还没有见过level 3的警告,根据stack over flow上面讲的当发生level 3警告时,系统内核将会接管,很有可能关掉IOS的主界面进程(SpringBorad),甚至会重启,照这个意思来说我们程序里面接收不到,也实属正常,系统自己的东西都被干掉了,我们肯定早被kill了。

2.low-memory处理思路:

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


3.low-memory处理方法:

有限的内存空间,使得iOS设备的内存常常吃紧,这时系统会向运行中的应用发出低内存警告,从而调用UIApplicationDelegate的方法:

- (void)applicationDidReceiveMemoryWarning:(UIApplication*)application

 

      实现这个代理方法时应该通过清除能够被重新创建或加载的缓存数据或对象来释放尽量多的内存。并且要结合UIViewControllerdidReceiveMemoryWarning方法和UIApplicationDidReceiveMemoryWarningNotification通知来使用。强烈建议你实现这个方法。如果你的应用在低内存条件下不释放足够多的内存,系统可能会彻底终止你的应用。

所以,如何正确实现这两个方法是保证应用在低内存环境下能够继续正常使用的关键。


iOSUIViewController 类给我们提供了处理内存不足的接口。在iOS 3.0 之前,当系统的内存不足时,UIViewControllerdidReceiveMemoryWarining 方法会被调用,我们可以在didReceiveMemoryWarining 方法里释放掉部分暂时不用的资源。

iOS3.0 开始,UIViewController增加了viewDidUnload方法。该方法和viewDIdLoad相配对。当系统内存不足时,首先UIViewControllerdidReceiveMemoryWarining 方法会被调用,而didReceiveMemoryWarining 会判断当前ViewControllerview是否显示在window上,如果没有显示在window上,则didReceiveMemoryWarining 会调用superdidReceiveMemoryWarning自动将viewcontroller 以及其所有子view全部销毁,然后调用viewcontrollerviewdidunload方法。所以我们不能将controllerview再次释放。如果当前UIViewControllerview显示在window上,则不销毁该viewcontrollerview,当然,viewDidunload也不会被调用了。所以我们需要在 viewDidUnload 方法中释放所有的 outlets(IBOutlet对象),以及可再次创建的资源。当前可见的 view controller 通过 didReceiveMemotyWarning合理释放资源。

但是到了ios6.0之后,这里又有所变化,ios6.0内存警告的viewDidUnload 被屏蔽,即又回到了ios3.0的时期的内存管理方式。它会调用didReceiveMemoryWarning内调用superdidReceiveMemoryWarning只是释放controllerresouse,不会释放view

4.low-memory处理例子:

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

Java代码  

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


句一个简单的例子,有这样一个 view controller

@interface MyViewController: UIVeiwController {

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 didREceiveMemotyWarning];

// 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];

}

 

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

    -(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. outlets置为 weak

view dealloc时,没有人握着任何一个指向 subviews 的强引用,那么 subviews 实例变量将会自动置空。

@property (nonatomic, weak) IBOutlet UITableView *tableView;

2. didReceiveMemotyWarning中将缓存数据置空

#pragma mark -

#pragma mark Memory management

- (void)didReceiveMemoryWarning

{

[super didRedceiveMemoryWarning];

// Dispose of any resources that can be recreated.

dataArray = nil;

}

不要忘记一点,每当 tableview reload的时候,需要判断一下 dataArray,若为空则重新创建。


5.注意事项:

苹果官方文档提到:

需要注意的是,如果要使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,原因如下:

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

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

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

4、在iOS6系统中,当系统发出MemoryWarning时,系统会自动回收bitmap类,但是不回收UIViewCALayer类。这样既回收了大部分内存,又能在需要bitmap类时,通过调用UIViewdrawRect方法重建。

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

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

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

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

6.模拟器模拟低内存警告:

使用真机调试程序时,比较难复现低内存环境,我们可以使用模拟器来模拟低内存警告,操作步骤如下图所示:

0 0
原创粉丝点击