cocos2d与Autorelease Pool

cooclc 2011-08-15

cocos2d创建的autorelease对象

cocos2d中,只要不是使用alloc方法创建的对象,都会自动发送autorelease消息

使用cocos2d的模版创建一个应用程序后,会发现只有main.m里面创建了一个AutoreleasePool。除此以外应用程序再无AutoreleasePool的踪迹。而cocos2d中所有非init方法创建的对象都不需要开发者向这些对象发送release消息。

这样一来,就有一个很困惑的问题出现:运行过程中创建的对象何时释放呢?

要搞明白这个问题,得先复习一下AutoreleasePool的原理。

AutoreleasePool

AutoreleasePool是一个NSAutoreleasePool对象的实例(下文将NSAutoreleasePool的实例称为pool)。pool可以看作一个容器,当给对象发送autorelease消息时,对象就会被放入容器。

在需要释放内存时,给pool发送drain或release消息,则pool会遍历容器中的所有对象,给每一个对象发送一个release消息。

参考如下代码:

MyClass.h

#import

@interfaceMyClass:NSObject{

}

@end

MyClass.m

#import"MyClass.h"

@implementationMyClass

-(void)release

{

NSLog(@"[MyClassrelease]");

[superrelease];

}

-(void)dealloc

{

NSLog(@"[MyClassdealloc]");

[superdealloc];

}

@end

main.m

#import

#import"MyClass.h"

intmain(intargc,char*argv[]){

NSAutoreleasePool*pool=[NSAutoreleasePoolnew];

MyClass*obj1=[[MyClassalloc]init];

NSLog(@"beforeautorelease-obj1retainCount:%d",[obj1retainCount]);

[obj1autorelease];

NSLog(@"afterautorelease-obj1retainCount:%d",[obj1retainCount]);

MyClass*obj2=[[MyClassalloc]init];

NSLog(@"beforeautorelease-obj2retainCount:%d",[obj2retainCount]);

NSLog(@"before[poolrelease]");

[poolrelease];

NSLog(@"after[poolrelease]");

[obj2release];

return0;

}

执行结果

beforeautorelease-obj1retainCount:1

afterautorelease-obj1retainCount:1

beforeautorelease-obj2retainCount:1

before[poolrelease]

[MyClassrelease]

[MyClassdealloc]

after[poolrelease]

[MyClassrelease]

[MyClassdealloc]

•从执行结果可以看到[[MyClassalloc]init]之后,obj1和obj2的retainCound均为1。

•其后的[obj1autorelease]虽然没有改变obj1的retainCound的值,但却将obj1放入了pool中。

•因此在[poolrelease]时,pool自动向obj1发送了release消息。而obj2则需要开发者自己发送release消息来释放。

cocos2d创建的autorelease对象

cocos2d中,只要不是使用alloc方法创建的对象,都会自动发送autorelease消息。例如CCSprite的spriteWithFile方法:

+(id)spriteWithFile:(NSString*)filename

{

return[[[selfalloc]initWithFile:filename]autorelease];

}

但事实上CCScene、CCLayer、CCSprite等对象都会在适当的时候被释放掉(例如切换场景)。要证实这个问题,可以给我们的对象增加dealloc方法,并在其中使用NSLog()来输出调试信息。

可main.m中创建的AutoreleasePool只会在应用程序结束时才释放,那这些autorelease对象是怎么被释放掉的呢?

事件循环与AutoreleasePool

仔细查阅Apple的文档,注意到文档中提到了一段话:TheApplicationKitcreatesanautoreleasepoolonthemainthreadatthebeginningofeverycycleoftheeventloop,anddrainsitattheend,therebyreleasinganyautoreleasedobjectsgeneratedwhileprocessinganevent.

大概意思就是ApplicationKit(iOS应用的基础框架)的主线程会在每一个事件循环开始时创建一个AutoreleasePool,然后在事件循环结束时释放这个pool。因此在事件循环期间创建的所有autorelease对象都会收到一个release消息。

参考文档:NSAutoreleasePoolClassReference,ThreadingProgrammingGuide–RunLoops,NSRunLoopClassReference。

由于iOS应用本质上是事件驱动的。当外部事件(触摸、设备信息、网络通讯、计时器)发生时,就会进入一个事件循环。所以事件循环在iOS应用中是频繁出现的。那么cocos2d的事件循环怎么产生的呢?

cocos2d中的事件循环

cocos2d中,CCDirector充当了整个游戏的场景调度器。在cocos2d/Platforms/iOS/CCDirectorIOS.m中可以找到如下代码:

-(void)startAnimation

{

.....

displayLink=[NSClassFromString(@"CADisplayLink")displayLinkWithTarget:self

selector:@selector(mainLoop:)];

[displayLinksetFrameInterval:frameInterval];

[displayLinkaddToRunLoop:[NSRunLoopcurrentRunLoop]

forMode:NSDefaultRunLoopMode];

}

这里的CADisplayLink是iOS3.1及更新版本内置的对象,可以按照屏幕刷新率(iOS设备目前是每秒60次,也就是60Hz)产生事件循环。所以可以看到startAnimation方法随后将[NSRunLoopcurrentRunLoop]添加到CADisplayLink中。RunLoop就是应用程序的事件循环对象了,因此cocos2d应用默认情况下每秒会产生60次事件循环(这里只考虑默认设置,并且忽略了延迟对屏幕更新次数的影响)。

CADisplayLink参考文档:OpenGLESProgrammingGuideforiOS–DrawingWithOpenGLES–RenderingUsinganAnimationLoop,CADisplayLinkClassReference。

但是在cocos2d应用中,事件循环过程中创建的很多对象并不都是在AutoreleasePool被释放时就被释放掉的。那这些对象哪里去了呢?

再次说明AutoreleasePool一个重点:AutoreleasePool并不释放对象,只是向对象发送release消息。

假设我们的游戏进入后有一个PLAY按钮,点击该按钮执行下列代码:

-(void)onPlayButtonTouch:(id)sender

{

CCDirector*director=[CCDirectorsharedDirector];

MyScene*scene=[MyScenenode];

[directorreplaceScene:scene];

}

这段代码构造了MyScene的实例,并提供给CCDirector对象进行管理。由于[CCDirectorreplaceScene]方法会向MyScene的实例发送retain消息,所以在PLAY按钮点击这个事件循环结束后,MyScene的实例并没有被释放:

//PLAY按钮点击事件循环开始

NSAutoreleasePool*pool=[NSAutoreleasePoolnew];

//执行PLAY按钮点击事件

-(void)onPlayButtonTouch:(id)sender

{

CCDirector*director=[CCDirectorsharedDirector];

MyScene*scene=[MyScenenode];//[sceneretainCount]=1

[directorreplaceScene:scene];//[sceneretainCound]=2

}

//PLAY按钮点击事件循环结束,自动创建的pool被释放

[poolrelease];

//此时[sceneretainCound]=1

//事件循环结束后,MyScene的实例并没有被释放,因为其retainCount大于0。

MyScene虽然没有被释放,但在下一次调用[CCDirectorreplaceScene]转到其他场景时,CCDirector就会向MyScene的实例发送release消息。此时MyScene实例的retainCount就会变成0,从而被正确释放掉。

至此,真相大白。

更多

内存管理是个复杂的问题,直到这篇博客写完之前我也没搞明白iOS的AutoreleasePool到底是怎么回事,所以有了前面一篇应急的处理办法:http://www.dualface.com/index.php/archives/1214。

彻底搞清楚iOS和cocos2d的内存管理后,上面的应急措施就显得多余了。不过全部通过属性访问的好处是不用记得在必须的时候向对象发送retain和release消息,编译器的@property和@synthesize指令已经帮你做好上述工作了。

其实不只是cocos2d应用,所有的iOS/MacOS应用都存在事件循环,因此整个Cocoa框架中,只要不是使用alloc方法创建的对象都不需要开发者自行发送release消息。Objective-C和iOS/MacOS系统的事件驱动模型已经达到了高度的协调性

相关推荐

最美应用有价值的好应用 / 0评论 2018-02-01