Cocos2d-x内存管理-绕不过去的坎

Cocos2d-x内存管理-绕不过去的坎,第1张

概述Cocos2d-x引擎的核心是用C++编写的,那对于所有使用该引擎的游戏开发人员来说,内存管理是一道绕不过去的坎。 关于Cocos2d-x内存管理,网上已经有了许多参考资料,有些资料写的颇为详实,因为在内存管理这块我不想多费笔墨,只是更多的将思路描述清 楚。 一、对象内存引用计数 Cocos2d-x内存管理的基本原理就是对象内存引用计数,Cocos2d-x将内存引用计数的实现放在了顶层父类CCOb

Cocos2d-x引擎的核心是用C++编写的,那对于所有使用该引擎的游戏开发人员来说,内存管理是一道绕不过去的坎。

关于Cocos2d-x内存管理,网上已经有了许多参考资料,有些资料写的颇为详实,因为在内存管理这块我不想多费笔墨,只是更多的将思路描述清 楚。

一、对象内存引用计数

Cocos2d-x内存管理的基本原理就是对象内存引用计数,Cocos2d-x将内存引用计数的实现放在了顶层父类CCObject中,这里将涉及引用计数的CCObject的成员和方法摘录出来:

class CC_DLL CCObject : public CCcopying{public:   … …protected:    // count of references    unsigned int        m_uReference;    // count of autorelease    unsigned int        m_uautoReleaseCount;public:    voID release(voID);    voID retain(voID);    CCObject* autorelease(voID);    … ….}CCObject::CCObject(voID): m_nLuaID(0),m_uReference(1) // when the object is created,the reference count of it is 1,m_uautoReleaseCount(0){  … …}voID CCObject::release(voID){    CCAssert(m_uReference > 0,"reference count should greater than 0");    –m_uReference;    if (m_uReference == 0)    {        delete this;    }}voID CCObject::retain(voID){    CCAssert(m_uReference > 0,"reference count should greater than 0");    ++m_uReference;}CCObject* CCObject::autorelease(voID){    CCPoolManager::sharedPoolManager()->addobject(this);    return this;}



先不考虑autorelease与m_uautoReleaseCount(后续细说)。计数的核心字段是m_uReference,可以看到:

* 当一个Object初始化(被new出来时),m_uReference = 1;
* 当调用该Object的retain方法时,m_uReference++;
* 当调用该Object的release方法时,m_uReference–,若m_uReference减后为0,则delete该Object。

二、手工对象内存管理

在上述对象内存引用计数的原理下,我们得出以下Cocos2d-x下手工对象内存管理的基本模式:

CCObject *obj = new CCObject();obj->init();…. …obj->release();在Cocos2d-x中CCDirector就是一个手工内存管理的典型:CCDirector* CCDirector::sharedDirector(voID){    if (!s_SharedDirector)    {        s_SharedDirector = new CCdisplaylinkDirector();        s_SharedDirector->init();    }    return s_SharedDirector;}voID CCDirector::purgeDirector(){    … …    // delete CCDirector    release();}


三、自动对象内存管理

所谓的“自动对象内存管理”,指的就是哪些不再需要的object将由Cocos2d-x引擎替你释放掉,而无需你手工再调用Release方法。

自动对象内存管理显然也要遵循内存引用计数规则,只有当object的计数变为0时,才会释放掉对象的内存。

自动对象内存管理的典型模式如下:

CCYourClass *CCYourClass::create(){    CCYourClass*pRet = new CCYourClass();    if (pRet && pRet->init())    {        pRet->autorelease();        return pRet;    }    else    {        CC_SAFE_DELETE(pRet);        return NulL;    }}


一般我们通过一个单例模式创建对象,与手工模式不同的地方在于init后多了一个autorelease调用。这里再把autorelease调用的实现摘录一遍:

CCObject* CCObject::autorelease(voID){ CCPoolManager::sharedPoolManager()->addobject(this); return this;}


追溯addobject方法:

// cocoa/CCautoreleasePool.cppvoID CCPoolManager::addobject(CCObject* pObject){    getCurReleasePool()->addobject(pObject);}voID CCautoreleasePool::addobject(CCObject* pObject){    m_pManagedobjectArray->addobject(pObject);    CCAssert(pObject->m_uReference > 1,"reference count should be greater than 1");    ++(pObject->m_uautoReleaseCount);    pObject->release(); // no ref count,in this case autorelease pool added.}// cocoa/CCArray.cppvoID CCArray::addobject(CCObject* object)                                                                                                   {                                                                                                                                              ccArrayAppendobjectWithResize(data,object);                             }  // support/data_support/ccCArray.cppvoID ccArrayAppendobjectWithResize(ccArray *arr,CCObject* object)                                                                          {                                                                                                                       ccArrayEnsureExtraCapacity(arr,1);                                                                  ccArrayAppendobject(arr,object);                                         }voID ccArrayAppendobject(ccArray *arr,CCObject* object){    CCAssert(object != NulL,"InvalID parameter!");    object->retain();    arr->arr[arr->num] = object;    arr->num++;}


调用层次挺深,涉及的类也众多,这里归纳总结一下。

Cocos2d-x的自动对象内存管理基于对象引用计数以及CCautoreleasePool(自动释放池)。引用计数前面已经说过了,这里单说自动释放池。Cocos2d-x关于自动对象内存管理的基本类层次结构如下:

    CCPoolManager类 (自动释放池管理器)        – CCArray*    m_pReleasePoolStack; (自动释放池栈,存放CCautoreleasePool类实例)               CCautoreleasePool类        – CCArray*    m_pManagedobjectArray; (受管对象数组)

CCObject关于内存计数以及自动管理有两个字段:m_uReference和m_uautoReleaseCount。前面在手工管理模式下,我只提及了m_uReference,是m_uautoReleaseCount该亮相的时候了。我们沿着自动释放对象的创建步骤来看看不同阶段,这两个重要字段的值都是啥,代表的是啥含义:


CCYourClass*pRet = new CCYourClass();    m_uReference = 1; m_uautoReleaseCount = 0;pRet->init();                           m_uReference = 1; m_uautoReleaseCount = 0;pRet->autorelease();                    m_pManagedobjectArray->addobject(pObject); m_uReference = 2; m_uautoReleaseCount = 0;++(pObject->m_uautoReleaseCount);          m_uReference = 2; m_uautoReleaseCount = 1; pObject->release();                        m_uReference = 1; m_uautoReleaseCount = 1; 

在调用autorelease之前,两个值与手工模式并无差别,在autorelease后,m_uReference值没有变,但m_uautoReleaseCount被加1。

m_uautoReleaseCount这个字段的名字很容易让人误解,以为是个计数器,但实际上绝大多数时刻它是一个标识的角色,以前版本代码中有一个布尔字段m_bManaged,似乎后来被m_uautoReleaseCount替换掉了,因此m_uautoReleaseCount兼有m_bManaged的含义, 也就是说该object是否在自动释放池的控制之下,如果在自动释放池的控制下,自动释放池会定期调用该object的release方法,直到该 object内存计数降为0,被真正释放。否则该object不能被自动释放池自动释放内寸,需手工release。这个理解非常重要,再后面我们能用到 这个理解。

四、自动释放时机

通过autorelease我们已经将object放入autoreleasePool中,那究竟何时对象会被释放呢?答案是每帧执行一次自动内存对象释放 *** 作。

在“Hello,Cocos2d-x”一文中,我们讲过整个Cocos2d-x引擎的驱动机制在于GLThread的guardedRun函数,后者会 “死循环”式(实际帧绘制频率受到屏幕vertsym信号的影响)的调用Render的onDrawFrame方法实现,而最终程序会进入 CCDirector::mainLoop方法中,也就是说mainLoop的执行频率是每帧一次。我们再来看看mainLoop的实现:

voID CCdisplaylinkDirector::mainLoop(voID){    if (m_bPurgeDirecotorInNextLoop)    {        m_bPurgeDirecotorInNextLoop = false;        purgeDirector();    }    else if (! m_bInvalID)     {         drawScene();         // release the objects         CCPoolManager::sharedPoolManager()->pop();     }}


这次我们要关注的不是drawScene,而是 CCPoolManager::sharedPoolManager()->pop(),显然在游戏未退出 (m_bPurgeDirecotorInNextLoop决定)的条件下,CCPoolManager的pop方法每帧执行一次,这就是自动释放池执行 的起点。

voID CCPoolManager::pop(){    if (! m_pCurReleasePool)    {        return;    }     int nCount = m_pReleasePoolStack->count();    m_pCurReleasePool->clear();      if(nCount > 1)      {        m_pReleasePoolStack->removeObjectAtIndex(nCount-1);        m_pCurReleasePool = (CCautoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount – 2);    }}


真正释放对象的方法是m_pCurReleasePool->clear()

voID CCautoreleasePool::clear(){    if(m_pManagedobjectArray->count() > 0)    {        CCObject* pObj = NulL;        CCARRAY_FOREACH_REVERSE(m_pManagedobjectArray,pObj)        {            if(!pObj)                break;            –(pObj->m_uautoReleaseCount);        }        m_pManagedobjectArray->removeAllObjects();    }}voID CCArray::removeAllObjects()     {       ccArrayRemoveAllObjects(data);                    }voID ccArrayRemoveAllObjects(ccArray *arr)                    {                           while( arr->num > 0 )                          {                            (arr->arr[--arr->num])->release();                   }                    } 

不出预料,当前自动释放池遍历每个“受控制”Object,–m_uautoReleaseCount,并调用该object的release方法。

我们接着按释放流程来看看m_uautoReleaseCount和m_uReference值的变化:

CCPoolManager::sharedPoolManager()->pop(); m_uReference = 0; m_uautoReleaseCount = 0;

五、自动释放池的初始化

自动释放池本身是何时出现的呢?回顾一下Cocos2d-x引擎的初始化过程(androID版),引擎初始化实在Render的onSurfaceCreated方法中进行的,我们不难追踪到以下代码:

//hellocpp/jni/hellocpp/main.cppJava_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit {       //这里CCDirector第一次被创建    if (!CCDirector::sharedDirector()->getopenGLVIEw())    {        CCEGLVIEw *vIEw = CCEGLVIEw::sharedOpenGLVIEw();        vIEw->setFrameSize(w,h);        AppDelegate *pAppDelegate = new AppDelegate();        CCApplication::sharedApplication()->run();    }}   CCDirector* CCDirector::sharedDirector(voID){    if (!s_SharedDirector)    {        s_SharedDirector = new CCdisplaylinkDirector();        s_SharedDirector->init();      }    return s_SharedDirector;}bool CCDirector::init(voID){    setDefaultValues();    … …    // create autorelease pool    CCPoolManager::sharedPoolManager()->push();    return true;}


六、探寻Cocos2d-x内核对象的自动化内存释放

前面我们基本了解了Cocos2D-x的自动化内存释放原理。如果你之前翻看过一些Cocos2d-x的内核源码,你会发现很多内核对象都是通过单例模式create出来的,也就是说都使用了autorelease将自己放入自动化内存释放池中被管理。

比如我们在HelloCpp中看到过这样的代码:

//HelloWorldScene.cppbool HelloWorld::init() {     …. ….    // add "HelloWorld" splash screen"    CCSprite* pSprite = CCSprite::create("HelloWorld.png");    // position the sprite on the center of the screen    pSprite->setposition(ccp(visibleSize.wIDth/2 + origin.x,visibleSize.height/2 + origin.y));    // add the sprite as a child to this layer    this->addChild(pSprite,0);    … …}


CCSprite采用自动化内存管理模式create object(cocos2dx/sprite_nodes/CCSprite.cpp),之后将自己加入到HelloWorld这个cclayer实例 中。按照上面的分析,create结束后,CCSprite object的m_uReference = 1; m_uautoReleaseCount = 1。一旦如此,那么在下一帧时,该object就会被CCPoolManager释放掉。但我们在屏幕上依旧可以看到该Sprite的存在,这是怎么回事呢?

问题的关键就在this->addChild(pSprite,0)这行代码中。addChild方法实现在cclayer的父类CCNode中:

//  cocos2dx/base_nodes/CCNode.cppvoID CCNode::addChild(CCNode *child,int zOrder,int tag){    … …    if( ! m_pChildren )    {        this->childrenAlloc();    }    this->insertChild(child,zOrder);    … …}voID CCNode::insertChild(CCNode* child,int z){    m_bReorderChildDirty = true;    ccArrayAppendobjectWithResize(m_pChildren->data,child);    child->_setZOrder(z);}voID ccArrayAppendobjectWithResize(ccArray *arr,CCObject* object){    ccArrayEnsureExtraCapacity(arr,1);    ccArrayAppendobject(arr,object);}voID ccArrayAppendobject(ccArray *arr,"InvalID parameter!");    object->retain();    arr->arr[arr->num] = object;    arr->num++;}


又是一系列方法调用,最终我们来到了ccArrayAppendobject方法中,看到了陌生而又眼熟的retain方法调用。

在本文开始我们介绍CCObject时,我们知道retain是CCObject的一个方法,用于增加m_uReference计数。而实际上retain还隐含着“保留”这层意思。

在完成this->addChild(pSprite,0)调用后,CSprite object的m_uReference = 2; m_uautoReleaseCount = 1,这很关键。

我们在脑子里再过一下自动释放池释放object的过程:–m_uReference,–m_uautoReleaseCount。一帧之后,两个值变成了m_uReference = 1; m_uautoReleaseCount = 0。还记得前面说过的m_uautoReleaseCount的另外一个非计数含义么,那就是表示该object是否“受控”,现在值为0,显然不再受自动释放池的控制了,后续即便再执行100次内存自动释放,也不会影响到该object的存活。

后续要想释放这个“精灵”,我们还是需要手工调用release,或再调用其autorelease方法。

原文http://tonybai.com/2014/03/18/cocos2dx-memory-management/ 总结

以上是内存溢出为你收集整理的Cocos2d-x内存管理-绕不过去的坎全部内容,希望文章能够帮你解决Cocos2d-x内存管理-绕不过去的坎所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1069711.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-26
下一篇2022-05-26

发表评论

登录后才能评论

评论列表(0条)

    保存