Cocos2d-x中replaceScene引发崩溃的解决

最近在玩Cocos2d-x时发现连续两次replaceScene中,第二次的replaceScene会引发崩溃。replaceScene在cocos2d中的作用即场景转换,使用非常频繁,google了一下发现遇到这个问题的人非常多,但是缺乏令人信服的解决方案。

网上有两个可以解决表面问题的办法。一把replaceScener换成pushScene,二是在转换到新场景时使用渐变等效果。但是这俩方案无异于掩耳盗铃,没有解决根本问题。

崩溃的情境可以用下图表示

而引发崩溃的实际代码在:

void CCDirector::setNextScene(void)
{
    // 下面一句导至崩溃
    bool runningIsTransition = dynamic_cast(m_pRunningScene) != NULL;
    bool newIsTransition = dynamic_cast(m_pNextScene) != NULL;
    // ...
}

由pRunningScene的字面意思可推断它实际指向的是SceneB。那么崩溃的原因应该是此时SceneB的内存已经被释放掉了,通过在SceneB的析构函数上打断点能证实B的析构的确先于setNextScene。
我们知道,Cocos2D的对象都是自带引用计数的,那么问题便可定位到到SceneB的引用计数上了,而B的内存分配是在A->B的过程中进行的,其过程如下代码

CCScene* sceneA = SceneA::create();
sceneA->autorelease();
CCDirector::sharedDirector()->replaceScene(sceneA);

初看之下,create()引起sceneA的引用计数为1,紧接着的autorelease使之在下个帧事件中-1,至于replaceScene中的引用计数我们就不用关心了,库总不可能出错吧?
那么,难道是不能做autorelease?搜索”cocos2d-x scene跳转方法”,发现不少例子也在这里加上了release方法的。百般无奈下,我想既然B的引用计数存在问题,那么A会为什么会幸免呢?于是只好翻出由工程模版自动生成的App->SceneA的代码:

bool AppDelegate::applicationDidFinishLaunching()
{
    // create a scene. it's an autorelease object 〈--关键所在
    CCScene *pScene = StartupScene::create();

    // run
    pDirector->runWithScene(pScene);

    return true;
}

在applicationDidFinishLaunching由自动生成的注释中,我发现了这样一句诡异的话“create a scene. it’s an autorelease object ”,什么叫autorelease object ?这有点超出我以往的认知范围了。在引用计数编程中,若有工厂函数返回一个对象引用,一般都会在结束前对引用计数+1,这是大家所约定俗成的。典型情境如COM中的QueryInterface。autorelease object又是什么概念呢?总不可能工厂刚new出来,在尚未返回前又把它release了吧?于是我又点开了CREATE_FUNC宏的定义

#define CREATE_FUNC(__TYPE__) \
static __TYPE__* create() \
{ \
    __TYPE__ *pRet = new __TYPE__(); \
    if (pRet && pRet->init()) \
    { \
        pRet->autorelease(); \     // 吐血
        return pRet; \
    } \
    else \
    { \
        delete pRet; \
        pRet = NULL; \
        return NULL; \
    } \
}

看到“pRet->autorelease()”不禁一股老血喷涌而出,心中大骂cocos你怎么不按套路出牌啊!有过object-c编程经验的人应该都明白是怎么一回事了。在create返回pRet之前,pRet就已经做了autorelease,而之前我在外部又做了一次autorelease,多减的一次引用计数就发生在这里。

的确,按照c++/COM的编程思想,在create()里做release,肯定是要被同寮们指着脊梁骨骂的。但cocos2D的特殊性在于,它最初是应用于ios并由object-c编写的。发展到cocos2d-X之后,虽然能用c++写了,但是autorelease这一套原本是属于object-c的概念也顺带迁移了过来,便发生了刚刚这一幕看似违反引用计数编程原则的事。在ios中,除非直接alloc,大部分的API返回的对象都是经过了autorelease的(准确的说是objc_retainAutoreleaseReturnValue)。cocos2d与ios对autorelease的实现还略有不同,这还得从cocos2d那个单线程的主循环帧驱动说起,关于autorelease的本质,我这里就不展开讲了,网上的讲解多得是。其实,我觉得如果把autorelaease改名成delayrelease,会容易理解很多。

Over, issue fixed!

10 Responses

    1. 嗯,是的。虽然我是做过OC的,但是又因为我也是做过一段时间的COM/ATL开发的,思维潜意识里觉得c++代码中获得对象就应该只是引用计数加1而不是自动释放。cocos2d-x在这方面为了保持与coco2d-iphone的一致性,刚入手时觉得这种内存管理相当别扭。

  1. 借宝地发泄一下
    cocos2dx 这个引擎我内心深处已经骂到无力再骂了. 虽然网上赞誉一大片,农民级公司都是用它开发, 农民级的dever拥趸一大堆, 好像大家都被绑架着了. 但我还是想说, 这个引擎充分体现了错误恶心的C++代码 长什么样. 从点开它的源代码文件夹的第一天起, 我就实在无法改变对它的看法.哪个家伙写C++代码这个德行,我立即叫他滚蛋. 以下文字带有深深的歧视.
    0. 用户应该是 出身 页游的coder更多.
    1. 里面各种防御性编程,我要疯了!!姑且抛开这个超级过时的理念是否正确不说, 光里面一大堆的 if(ptr!=NULL){ delete ptr; ptr=NULL } 就让广大cpper无比蛋疼了. 2dx的作者, 你能找本C++的语法书看完再开始写代码么?? 亲手delete 一个NULL 再写代码会死人么???
    2. 它的内存管理方式 完全是非C++的, 大点的模块谁不用智能指针, 这里却蜕化到使用OC的管理方式, 我终于明白为什么那么多人大骂C++的内存管理了. =.=||
    3. 里面不少trick的做法. 代码随意点, 赶工, 谁都能理解. 但名声起来了, 支持者多了后, 能不能少花时间加蛋疼的feature 多花点时间把恶心的代码先fix一下 ??
    4. 好吧. 只怪自己没本事,钱少人少.有本事的公司, 看都看不看2dx一眼. 下个游戏 坚持换引擎了,再麻烦也比无奈的被绑架好.
    5. 每看有 小白以2dx为例 赞誉 C++语言的时候,我都不知道该说什么了…….

    最后,向农民叔叔表示抱歉,这个词太好用了,我也没办法.

发表评论

电子邮件地址不会被公开。 必填项已用*标注