您的位置:首页 > 移动开发 > Cocos引擎

cocos2d内存管理

2012-09-26 23:15 239 查看
本文将针对内存管理和自动释放消息的内容进行一些讨论。

通常来说,当在Objective-C中创建一个对象时会调用alloc方法。一旦调用alloc方法,就有责任在不再需要该对象时释放它。以下代码展示了最典型的alloc/init和release循环:

// allocate a new instance of NSObject
NSObject* myObject = [[NSObject alloc] init];

// do something with myObject here ...

// release the memory used by myObject
// if you don't release it, the object is leaked and the memory used by it
// is never freed.
[myObject release];


由于iOS应用程序总是使用自动释放池,因此可以通过调用autorelease消息来避免发送release消息。下面是用autorelease重写后的代码示例:
// allocate a new instance of NSObject
NSObject* myObject = [[[NSObject alloc] init] autorelease];

// do something with myObject here ...

// no need to call release, in fact you should not send release as it would crash


可见,autorelease的出现从某种意义来说简化了内存管理问题,因为你再也不需要记着发送release消息了。自动释放池为你做到了这一点,它会在晚一些时候对池中的各个对象发送release消息。而增加autorelease消息对创建对象来说,也只是增添了一点点复杂度而已。

请看以下代码,它遵循常规风格来创建和释放CCNode对象:
// allocate a new instance of CCNode
CCNode* myNode = [[CCNode alloc] init]];
// do something with myNode ...
[myNode release];


大家普遍不倾向于使用这种创建cocos2d对象的方式。用静态初始化方法更容易一些,而且将返回一个会被自动释放的对象。与苹果官方推荐的方法相反,cocos2d对于autorelease的使用已经内置到了引擎的设计中:它把诸如[[[NSObject alloc] init] autorelease]等调用写入了类的静态方法中。这是一件好事!这种设计让你不必再时刻盘算着哪些对象需要被释放,它使你避免了由于过度释放或内存泄漏而导致的程序崩溃风险。

CCNode类的静态初始化方法是"+(id) node"。以下代码将向self发送alloc消息,这等效于在CCNode的实现中调用[CCNode alloc]:
+(id) node {
return [[[self alloc] init] autorelease];
}


上面对self的调用方法更加普遍,而且对于C++程序员来说应该也更容易接受。

现在可以用静态初始化方法来重写CCNode的生成方法。不出所料,代码变得很简洁:

// allocate a new instance of CCNode
CCNode* myNode = [CCNode node];
// do something with myNode …


这就是使用自动释放对象的妙处。你不必再记着给对象发送释放信息。每一次cocos2d进入下一帧,那些不再使用的自动释放对象将被自动地释放。但这样做也有一个缺点:如果使用上述代码,然后在下一帧或者以后想要访问 myNode对象时,你就会发现它已经不在内存中了。如果这时发送消息给它,将导致程序出现EXC_BAD_ACCESS错误而崩溃。

简单地把CCNode* myNode变量当作类成员变量并不意味着对象使用的内存会被自动保留下来。如果想在下一帧或者以后的帧中访问自动释放对象,就必须保留它。并且,如果没有显式地将其添加为子节点,之后还是要对其进行手动释放。

有一种可以更好地使用自动释放对象的方法,并且不需要显式地调用retain方法:可以将生成的CCNode对象作为子节点添加到另一个派生自CCNode 的对象中,甚至可以删除成员变量而直接依赖cocos2d来保存对象:

// creating an autorelease instance of CCNode
-(void) init
{
myNode = [CCNode node];
myNode.tag = 123;
// adding the node as children to self (assuming self is derived from CCNode)
[self addChild:myNode];
}
-(void) update:(ccTime)delta
{
// later access and use the myNode object again
CCNode* myNode = [self getChildByTag:123];
// do something with myNode
}


addChild将CCNode对象添加到了一个集合中,本例使用了CCArray。CCArray与iPhone SDK 的NSMutableArray类似,但是效率比NSMutableArray更高。CCArray、NSMutableArray,还有iPhone SDK中的任何其他集合都会自动地向每一个添加进来的对象发送retain消息,也会对每个要删除的对象发送release 消息。所以,这样生成的对象可以一直存在,并保持有效且可访问状态。但是,当它们从集合中删除以后,对象也会被自动释放。

上述方法对cocos2d对象来说是最好的内存管理方式。有些开发者可能会告诉你自动释放不好或者速度很慢,不可听信它们。

注意:

苹果官方的开发者文档建议减少使用自动释放对象,但是大多数 cocos2d 对象都是自动释放对象,这样能使内存管理更为简单。

如果使用alloc/init和release来管理每一个cocos2d对象,那么会遇到很多麻烦,而且收效甚微。我不是说你永远都不会用到alloc/init;它们确实是有用的,而且有时候你可能必须使用它们。但是对于 cocos2d 对象来说,应该依赖于静态的自动释放初始化方法。

自动释放对象只有一个缺点,那就是在游戏进入下一帧前,这些对象将一直占用内存。这就意味着,如果你在每一帧都生成许多很快就要被丢弃的自动释放对象,可能会浪费很多内存。不过这样的情况很少发生。

下面的两条规则可以作为对cocos2d内存管理机制的总结,Objective-C的内存管理始终遵循着这两条规则:

如果你拥有(通过alloc、copy或retain得到)一个对象,就必须在用完之后释放它。

如果你已经向一个对象发送了自动释放消息,就不该再release它。

官网:


观察低内存警告

当系统向您的应用程序发送低内存警告时,您需要加以注意。当可用内存的数量降低到安全阈值以下时,iPhone OS会通知最前面的应用程序。如果您的应用程序收到这种警告,就必须尽可能多地释放内存,即释放不再需要的对象或清理易于在稍后进行重建的缓存。

UIKit提供如下几种接收低内存警告的方法:

在应用程序委托中实现
applicationDidReceiveMemoryWarning:
方法。

在您的
UIViewController
子类中实现
didReceiveMemoryWarning
方法。

注册
UIApplicationDidReceiveMemoryWarningNotification
通告

一旦收到上述的任何警告,您的处理代码就应该立即响应,释放所有不需要的内存。视图控制器应该清除当前离屏的视图对象,您的应用程序委托则应该释放尽可能多的数据结构,或者通知其它应用程序对象释放其拥有的内存。

如果您的定制对象知道一些可清理的资源,则可以让该对象注册
UIApplicationDidReceiveMemoryWarningNotification
通告,并在通告处理器代码中直接释放那些资源。如果您通过少数对象来管理大多数可清理的资源,且适合清理所有的这些资源,则同样可以让这些对象进行注册。但是,如果您有很多可清理的对象,或者仅希望释放这些对象的一个子集,则在您的应用程序委托中进行释放可能更好一些。

重要提示:和系统的应用程序一样,您的应用程序总是需要处理低内存警告,即使在测试过程中没有收到那些警告,也一样要进行处理。系统在处理请求时会消耗少量的内存。在检测到低内存的情况时,系统会将低内存警告发送给所有正在运行的进程(包括您的应用程序),而且可能终止某些后台程序(如果必要的话),以减轻内存的压力。如果释放后内存仍然不够—可能因为您的应用程序发生泄露或消耗太多内存—系统仍然可能会终止您的应用程序。

表1-7 减少应用程序内存印迹的技巧

技巧
采取的措施
消除内存泄露
由于内存是iPhone OS的关键资源,所以您的应用程序不应该有任何的内存泄露。存在内存泄露意味着应用程序在之后可能没有足够的内存。您可以用Instruments程序来跟踪代码中的泄露,该程序既可以用于仿真器,也可以用于实际的设备。有关如何使用Instruments的更多信息,请参见Instruments用户指南
使资源文件尽可能小
文件驻留在磁盘中,但在使用时需要载入内存。属性列表文件和图像文件是通过简单的处理就可以节省空间的两种资源类型。您可以通过
NSPropertyListSerialization
类将属性列表文件存储为二进制格式,从而减少它们的使用空间;对于图像,可以将所有图像文件压缩得尽可能小(PNG图像是iPhone应用程序的推荐图像格式,可以用
pngcrush
工具来进行压缩)。
使用Core Data或SQLite来处理大的数据集合
如果您的应用程序需要操作大量的结构化数据,请将它存储在Core Data的持久存储或SQLite数据库,而不是使用扁平文件。Core Data和SQLite都提供了管理大量数据的有效方法,不需要将整个数据一次性地载入内存。

Core Data的支持是在iPhone OS 3.0系统上引入的。
延缓装载资源
在真正需要资源文件之前,永远不应该进行装载。预先载入资源文件表面看好象可以节省时间,但实际上会使应用程序很快变慢。此外,如果您最终没有用到那些资源,预先载入将只是浪费内存。
将程序连编为Thumb格式
加入
-mthumb
开关可以将代码的尺寸减少最多达35%。但是,对于具有大量浮点数运算的代码模块,请务必将这个选项关闭,因为对那样的模块使用Thumb反而会导致性能的下降。


恰当地分配内存

iPhone应用程序使用委托内存模式,因此,您必须显式保持和释放内存。表1-8列出了一些在程序中分配内存的技巧。

表1-8 分配内存的技巧

技巧
采取的措施
减少自动释放对象的使用
通过
autorelease
方法释放的对象会留在内存中,直到显式清理自动释放池或者程序再次回到事件循环。在任何可能的时候,请避免使用
autorelease
方法,而是通过
release
方法立即收回对象占用的空间。如果您必须创建一定数量的自动释放对象,则请创建局部的自动释放池,以便在返回事件循环之前定期对其进行清理,回收那些对象的内存。
为资源设置尺寸限制
避免装载大的资源文件,如果有更小的文件可用的话。请用适合于iPhone OS设备的恰当尺寸图像来代替高清晰度的图像。如果您必须使用大的资源文件,需要考虑仅装载当前需要的部分。举例来说,您可以通过
mmap
munmap
函数来将文件的一部分载入内存或从内存卸载,而不是操作整个文件。有关如何将文件映射到内存的更多信息,请参见文件系统性能指南
避免无边界的问题集
无边界的问题集可能需要计算任意大量的数据。如果该集合需要的内存比当前系统能提供的还要多,则您的应用程序可能无法进行计算。您的应用程序应该尽可能避免处理这样的集合,而将它们转化为内存使用极限已知的问题。
有关如何在iPhone应用程序中分配内存及使用自动释放池的详细信息,请参见Cocoa基本原理指南文档的Cocoa对象部分。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息