浅析cocos2dx的内存管理机制
今天研究了一下Cocos2d-x的内存管理机制,有些地方不太好懂,花了不少时间,现在感觉理解得差不多了,赶紧记录下自己的思路,和广大朋友分享。如果大家发现有错误或不理解的地方,欢迎指正!
1. C++中变量的内存空间分配
在C++中创建类对象时,有两种内存分配方式:栈上分配和堆上分配。如果类对象在栈上分配内存空间,其内存管理由系统自动完成;而在堆上分配内存空间时,则需要手动使用delete进行释放。
Cocos2d-x采用的是在堆上分配内存空间的方式。在编写程序时,我们通常通过工厂方法获取Cocos2d-x类的指针,很少见到在栈上分配内存空间的情况。既然在堆上分配内存,那么如何管理这块内存以及何时释放就成了问题。在程序中,一个对象的内存空间可能被多个对象引用,如果过早删除该对象,而还有其他对象在引用这块内存,程序就会崩溃。因此,Cocos2d-x引入了引用计数的内存管理机制。
2. 引用计数机制
当在堆上分配一块内存空间创建对象时,该对象的引用计数初始为1。每当有新对象引用这块内存时,引用计数加1;当某个对象不再引用该内存时,引用计数减1。当引用计数减为0时,使用delete删除这块内存,这样就能保证在有对象引用时可以正常访问,引用结束后也能正常回收内存。
以下代码可以帮助我们更清楚地理解引用计数:
// 对象创建的时候引用计数被设置为1,这个是在它的构造函数中完成的,它会先调用父类CCObject的构造函数
// CCObject的构造函数如下所示
// CCObject::CCObject(void)
// , m_uReference(1) {}// when the object is created, the reference count of it is 1
CCSprite * sprite = new CCSprite();
CCLog("retain count:%d", sprite->retainCount());
// 调用retain方法的时候引用计数增加1
sprite->retain();
CCLog("retain count:%d", sprite->retainCount());
// 调用release方法的时候引用计数减一,当这个引用计数减为0的时候,在release方法中会delete掉这个对象
sprite->release();
CCLog("retain count:%d", sprite->retainCount());
// 当我们调用autorelease方法的时候会调用这段代码
// CCPoolManager::sharedPoolManager()->addObject(this);
// 调用autorelease方法的时候对象会被放到自动回收池中,这个自动回收池在每帧结束的时候会调用一次对象的release方法
sprite->autorelease();
CCLog("retain count:%d", sprite->retainCount());
3. 自动回收机制
上述代码中,autorelease方法的自动回收机制不太容易理解,下面我们详细分析。
首先,需要明确“帧”的概念。我们常说的每秒多少帧,这里每帧的执行时间并不固定,它取决于每帧需要完成的任务量。如果每帧需要渲染大量内容,那么这一帧的执行时间就会变长,游戏会变得卡顿,帧率也会下降。因此,不是时间决定帧率,而是帧的执行任务量影响时间。
自动回收池在每帧结束时发挥作用。在游戏的每一帧开始前,系统会创建一个内存回收池。当我们调用autorelease方法时,对象会被放入这个内存回收池。当一帧结束时,内存回收池会释放,池中的对象会被调用一次release方法,即引用计数减1。如果此时引用计数为0,对象将被删除;如果引用计数不为0,对象不会被删除。下一帧开始时,系统会创建新的内存回收池,上一帧添加的对象不会重新添加,新的内存回收池只包含本帧调用autorelease方法的对象。
下面通过代码分析自动回收机制的工作原理:
// 在create的时候调用了sprite的autorelease方法
CCSprite * sprite = CCSprite::create("HelloWorld.png");
CCLog("retain count:%d", sprite->retainCount()); // retain 1
this->addChild(sprite);
CCLog("retain count:%d", sprite->retainCount()); // retain 2
调用create工厂方法时,内部先new一个CCSprite对象,引用计数加1,然后调用autorelease方法将对象放入自动回收池。由于这一帧还未结束,引用计数仍为1,所以第一次打印结果为1。调用addChild方法时,当前层接收该对象并将其引用计数加1,表示当前层正在使用这块内存,此时引用计数变为2。当这一帧结束时,自动回收池将对象的引用计数减1,此时只有CCLayer在引用该对象。当CCLayer析构时,会调用对象的release方法,从而删除CCSprite对象。
所谓自动回收机制,就是在每帧结束时,将对象创建时增加的引用计数减掉,让引擎中持有对象引用的其他类负责管理对象,当持有者析构时删除引用。引擎中的类通过retain和release方法来管理对象的生命周期,这就是自动回收的含义。
4. 内存管理机制实例分析
4.1 程序崩溃案例分析
bool HelloWorld::init()
{
if (!CCLayer::init())
{
return false;
}
CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
this,
menu_selector(HelloWorld::menuCloseCallback));
CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
this->addChild(pMenu, 1);
this->m_sprite = CCSprite::create("HelloWorld.png");
CCLog("%d", this->m_sprite->retainCount());
return true;
}
// 按钮的响应事件
void HelloWorld::menuCloseCallback(CCObject* pSender)
{
CCLog("%d", this->m_sprite->retainCount());
this->m_sprite->getPosition();
}
运行上述程序会崩溃。创建CCSprite对象时,引用计数为1,且对象被放入内存回收池。这一帧结束后,对象会被release一次,引用计数变为0,对象被释放。当按下按钮调用该对象时,由于内存已释放,程序就会崩溃。
4.2 修正后的代码分析
this->m_sprite = CCSprite::create("HelloWorld.png");
CCLog("%d", this->m_sprite->retainCount());
this->m_sprite->retain();
CCLog("%d", this->m_sprite->retainCount());
修改后的代码手动调用了retain方法,虽然自动回收池会将对象release一次,但引用计数仍为1。因此,在按钮的回调函数中调用该对象时,程序不会崩溃。需要注意的是,由于手动调用了retain,在层析构时要记得调用release,以避免内存泄漏。
4.3 CCArray的例子
bool HelloWorld::init()
{
bool bRet = false;
do
{
// 父类初始化
CC_BREAK_IF(!CCLayer::init());
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);
this->scheduleUpdate();
bRet = true;
} while (0);
return bRet;
}
void HelloWorld::update(ccTime dt)
{
refreshData();
}
在这个例子中,创建多个CCSprite对象并添加到当前层,同时将这些对象添加到CCArray中。在使用CCArray管理对象时,同样要遵循引用计数的规则,确保对象的引用计数正确管理,避免内存泄漏。
通过以上分析,我们对Cocos2d-x的内存管理机制有了更深入的理解。在开发过程中,要合理使用retain、release和autorelease方法,确保对象的生命周期得到正确管理。