cocos2d与Autorelease Pool
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系统的事件驱动模型已经达到了高度的协调性