Cocos2d-x中的引用计数(Reference Count)和自动释放池(AutoReleasePool)

2015年03月24日 15:38 0 点赞 0 评论 更新于 2025-11-21 18:28

引用计数

引用计数是C/C++项目中一种古老的内存管理方式。早在8年前我研究一款名为TCPMP的开源项目时,引用计数就已存在。

iOS SDK将这项技术封装到了NSAutoreleasePool中。在Cocos2d-x里,我们克隆了一套CCAutoreleasePool。两者的用法基本相同,若你未涉足iOS开发,可以查阅苹果官方文档NSAutoreleasePool Class Reference

CCAutoreleasePool

Cocos2d-x的CCAutoreleasePool与Cocoa的NSAutoreleasePool概念和API相同,但有两点重要区别:

  1. 无法手动创建:Cocos2d-x会为每个游戏创建一个自动释放池实例对象,游戏开发者不能新建自动释放池,只需专注于release/retain cocos2d::CCObject的对象。
  2. 不支持多线程CCAutoreleasePool不能用于多线程。若游戏需要网络线程,仅可在网络线程中接收数据、改变状态标志,切勿在该线程中调用Cocos2d接口。原因如下:

CCAutoreleasePool的逻辑是,当调用object->autorelease()时,对象会被放入自动释放池。自动释放池会维持对象的生命周期,直至当前消息循环结束。在消息循环的最后,若该对象未被其他类或容器retain过,它将自动释放。例如,layer->addChild(sprite)sprite添加到layer的子节点列表中,sprite的生命周期会持续到layer释放时,而非在当前消息循环的最后被释放。

这就是不能在网络线程中管理CCObject生命周期的原因。因为在每个UI线程的最后,自动释放对象会被删除,调用这些已删除的对象时会导致程序崩溃。

CCObject::release()、retain() 和 autorelease()

简而言之,只有两种情况需要调用release()方法:

  1. 手动new一个cocos2d::CCObject子类的对象,如CCSpriteCCLayer等。
  2. 获取cocos2d::CCObject子类对象的指针,并在代码中调用过retain方法。

以下是不需要调用retainrelease方法的例子:

CCSprite* sprite = CCSprite::create("player.png");

使用静态构造函数

CCSprite::create("player.png")是使用静态构造函数的示例。在Cocos2d-x中,除单例外的所有类都提供了静态构造函数,这些静态构造函数包含以下4项操作:

  1. 新建一个对象。
  2. 调用object->init(…)
  3. 若初始化成功(如成功找到纹理文件),则调用object->autorelease()
  4. 返回已标记为autorelease的对象。

所有CCAsdf::createWithXxxx(…)类型的函数都遵循上述方式。

在Cocos2d-x v1.x或更早版本中,使用方式如下:

CCSprite* sprite = CCSprite::spriteWithTexture(…);

使用这些静态构造函数时,无需关注newdeleteautorelease,只需关注object->retain()object->release()

一个错误的例子

有开发者报告了一个使用CCArray导致崩溃的例子:

bool HelloWorld::init()
{
bool bRet = false;
do
{
// super init first
CC_BREAK_IF(! CCLayer::init());

// add your codes below…
CCSprite* bomb1 = CCSprite::create("CloseNormal.png");
CCSprite* bomb2 = CCSprite::create("CloseNormal.png");
CCSprite* bomb3 = CCSprite::create("CloseNormal.png");
CCSprite* bomb4 = CCSprite::create("CloseNormal.png");
CCSprite* bomb5 = CCSprite::create("CloseNormal.png");
CCSprite* bomb6 = CCSprite::create("CloseNormal.png");

addChild(bomb1, 1);
addChild(bomb2, 1);
addChild(bomb3, 1);
addChild(bomb4, 1);
addChild(bomb5, 1);
addChild(bomb6, 1);

m_pBombsDisplayed = CCArray::create(bomb1, bomb2, bomb3, bomb4, bomb5, bomb6, NULL);
// m_pBombsDisplayed 是在头文件中被定义为一个 protected 变量.
// <--- 我们应该添加在这里m_pBombsDisplayed->retain()方法来防止在HelloWorld::refreshData()中crash。

this->scheduleUpdate();
bRet = true;
} while (0);
return bRet;
}

void HelloWorld::update(ccTime dt)
{
refreshData();
}

void HelloWorld::refreshData()
{
m_pBombsDisplayed->objectAtIndex(0)->setPosition(ccp(100, 100));
}

该错误在于m_pBombsDisplayed是使用CCArray::create(…)创建的,这是静态构造方式,数组被标记为autorelease。因此,该数组会在当前消息循环的最后被CCAutoreleasePool释放。

当后续消息循环调用HelloWorld::update(ccTime)时,m_pBombsDisplayed已成为野指针,从而导致崩溃。

为修复此崩溃问题,需在m_pBombsDisplayed = CCArray::create(…);之后添加m_pBombsDisplayed->retain(),并在HelloWorld::~HelloWorld()的析构函数中调用m_pBombsDisplayed->release()

作者信息

menghao

menghao

共发布了 3994 篇文章