cocos2d与Autorelease Pool

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


使用 cocos2d 的模版创建一个应用程序后,会发现只有 main.m 里面创建了一个 Autorelease Pool。除此以外应用程序再无 Autorelease Pool 的踪迹。而 cocos2d 中所有非 init 方法创建的对象都不需要开发者向这些对象发送 release 消息。
这样一来,就有一个很困惑的问题出现:运行过程中创建的对象何时释放呢?
要搞明白这个问题,得先复习一下 Autorelease Pool 的原理。
Autorelease Pool
Autorelease Pool 是一个 NSAutoreleasePool 对象的实例(下文将 NSAutoreleasePool 的实例称为 pool)。pool 可以看作一个容器,当给对象发送 autorelease 消息时,对象就会被放入容器。
在需要释放内存时,给 pool 发送 drain 或 release 消息,则 pool 会遍历容器中的所有对象,给每一个对象发送一个 release 消息。
参考如下代码:
MyClass.h
#import
@interface MyClass : NSObject {
}
@end
MyClass.m
#import "MyClass.h"
@implementation MyClass
- (void) release
{
NSLog(@"[MyClass release]");
[super release];
}
- (void) dealloc
{
NSLog(@"[MyClass dealloc]");
[super dealloc];
}
@end
main.m
#import
#import "MyClass.h"
int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
MyClass *obj1 = [[MyClass alloc] init];
NSLog(@"before autorelease - obj1 retainCount: %d", [obj1 retainCount]);
[obj1 autorelease];
NSLog(@" after autorelease - obj1 retainCount: %d", [obj1 retainCount]);
MyClass *obj2 = [[MyClass alloc] init];
NSLog(@"before autorelease - obj2 retainCount: %d", [obj2 retainCount]);
NSLog(@"before [pool release]");
[pool release];
NSLog(@"after [pool release]");
[obj2 release];
return 0;
}
执行结果
before autorelease - obj1 retainCount: 1
after autorelease - obj1 retainCount: 1
before autorelease - obj2 retainCount: 1
before [pool release]
[MyClass release]
[MyClass dealloc]
after [pool release]
[MyClass release]
[MyClass dealloc]
•从执行结果可以看到 [[MyClass alloc] init] 之后,obj1 和 obj2 的 retainCound 均为 1。
•其后的 [obj1 autorelease] 虽然没有改变 obj1 的 retainCound 的值,但却将 obj1 放入了 pool 中。
•因此在 [pool release] 时,pool 自动向 obj1 发送了 release 消息。而 obj2 则需要开发者自己发送 release 消息来释放。
cocos2d 创建的 autorelease 对象
cocos2d 中,只要不是使用 alloc 方法创建的对象,都会自动发送 autorelease 消息。例如 CCSprite 的 spriteWithFile 方法:
+(id)spriteWithFile:(NSString*)filename
{
return [[[self alloc] initWithFile:filename] autorelease];
}
但事实上 CCScene、CCLayer、CCSprite 等对象都会在适当的时候被释放掉(例如切换场景)。要证实这个问题,可以给我们的对象增加 dealloc 方法,并在其中使用 NSLog() 来输出调试信息。
可 main.m 中创建的 Autorelease Pool 只会在应用程序结束时才释放,那这些 autorelease 对象是怎么被释放掉的呢?
事件循环与 Autorelease Pool
仔细查阅 Apple 的文档,注意到文档中提到了一段话:The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.
大概意思就是 Application Kit(iOS 应用的基础框架)的主线程会在每一个事件循环开始时创建一个 Autorelease Pool,然后在事件循环结束时释放这个 pool。因此在事件循环期间创建的所有 autorelease 对象都会收到一个 release 消息。
参考文档:NSAutoreleasePool Class Reference, Threading Programming Guide – Run Loops, NSRunLoop Class Reference。
由于 iOS 应用本质上是事件驱动的。当外部事件(触摸、设备信息、网络通讯、计时器)发生时,就会进入一个事件循环。所以事件循环在 iOS 应用中是频繁出现的。那么 cocos2d 的事件循环怎么产生的呢?
cocos2d 中的事件循环
cocos2d 中,CCDirector 充当了整个游戏的场景调度器。在 cocos2d/Platforms/iOS/CCDirectorIOS.m 中可以找到如下代码:
- (void) startAnimation
{
.....
displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self
selector:@selector(mainLoop:)];
[displayLink setFrameInterval:frameInterval];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
}
这里的 CADisplayLink 是 iOS 3.1 及更新版本内置的对象,可以按照屏幕刷新率(iOS 设备目前是每秒 60 次,也就是 60Hz)产生事件循环。所以可以看到 startAnimation 方法随后将 [NSRunLoop currentRunLoop] 添加到 CADisplayLink 中。Run Loop 就是应用程序的事件循环对象了,因此 cocos2d 应用默认情况下每秒会产生 60 次事件循环(这里只考虑默认设置,并且忽略了延迟对屏幕更新次数的影响)。
CADisplayLink 参考文档:OpenGL ES Programming Guide for iOS – Drawing With OpenGL ES – Rendering Using an Animation Loop, CADisplayLink Class Reference。
但是在 cocos2d 应用中,事件循环过程中创建的很多对象并不都是在 Autorelease Pool 被释放时就被释放掉的。那这些对象哪里去了呢?
再次说明 Autorelease Pool 一个重点:Autorelease Pool 并不释放对象,只是向对象发送 release 消息。
假设我们的游戏进入后有一个 PLAY 按钮,点击该按钮执行下列代码:
- (void) onPlayButtonTouch:(id)sender
{
CCDirector *director = [CCDirector sharedDirector];
MyScene *scene = [MyScene node];
[director replaceScene:scene];
}
这段代码构造了 MyScene 的实例,并提供给 CCDirector 对象进行管理。由于 [CCDirector replaceScene] 方法会向 MyScene 的实例发送 retain 消息,所以在 PLAY 按钮点击这个事件循环结束后,MyScene 的实例并没有被释放:
// PLAY 按钮点击事件循环开始
NSAutoreleasePool *pool = [NSAutoreleasePool new];
// 执行 PLAY 按钮点击事件
- (void) onPlayButtonTouch:(id)sender
{
CCDirector *director = [CCDirector sharedDirector];
MyScene *scene = [MyScene node]; // [scene retainCount] = 1
[director replaceScene:scene]; // [scene retainCound] = 2
}
// PLAY 按钮点击事件循环结束,自动创建的 pool 被释放
[pool release];
// 此时 [scene retainCound] = 1
// 事件循环结束后,MyScene 的实例并没有被释放,因为其 retainCount 大于 0。
MyScene 虽然没有被释放,但在下一次调用 [CCDirector replaceScene] 转到其他场景时,CCDirector 就会向 MyScene 的实例发送 release 消息。此时 MyScene 实例的 retainCount 就会变成 0,从而被正确释放掉。
至此,真相大白。
更多
内存管理是个复杂的问题,直到这篇博客写完之前我也没搞明白 iOS 的 Autorelease Pool 到底是怎么回事,所以有了前面一篇应急的处理办法: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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值