cocos2dx內存管理


cocos2dx基於引用計數管理內存,所有繼承自CCObject的對象都將獲得引用計數的能力,可通過調用retain成員函數用於引用計數值,調用release減少引用計數值,當計數值減為0時銷毀對象.

cocos2dx的對象管理是樹形結構的,可通過調用父親節點的addChild成員函數將一個子節點對象添加到父節點中,當子節點被添加到父親節點中,子節點的引用計數值加1,如果通過removeChild將子節點從父節點中移除子節點的引用計數值減1.當父節點被銷毀時,會遍歷其所有的子節點,將其子節點的引用計數值減1,當子節點的計數值為0,銷毀子節點.

提到cocos2dx的內存管理,就不得不提autorelease.

我們隨便查看一個函數:

 

CCSprite* CCSprite::createWithTexture(CCTexture2D *pTexture)
{
    CCSprite *pobSprite = new CCSprite();
    if (pobSprite && pobSprite->initWithTexture(pTexture))
    {
        pobSprite->autorelease();
        return pobSprite;
    }
    CC_SAFE_DELETE(pobSprite);
    return NULL;
}

  

注意到CCSprite被生成並成功初始化之后(initWithTexture)立刻就調用了autorelease函數.那么autorelease的作用到底是什么.首先當一個對象A被創建出來之后它的引用計數值必定為1,如果我們將A添加到一個父節點B中,此時它的引用計數值為2.之后銷毀父節點B,依據上面的描述,父節點被銷毀時會將其子節點的引用計數減1.也就是說父節點B被銷毀之后A的引用計數值為1。那么我們就必須手動在不再需要A的時候調用releasedelete來將A銷毀.為了減輕程序員的負擔,cocos2dx提供了CCAutoreleasePool當節點A被添加到autoreleasepool之后,我們就不在需要關注A的銷毀了.因為引擎會在每幀結束之后清理autoreleasepool中的對象,調用它的release如果如果對象引用計數值降為0將對象銷毀.

下面是CCAutoreleasePool::addObject,autorelease最終調用的就是這個函數:

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.
}

CCAutoreleasePool中添加一個對象,底層管理容器是一個array對象被添加到array的時候不檢測重復性,所以個一對象可以被添加多次.添加完成后需要調用release函數減少引用計數,因為在array->addObject中調用了retain.

應當注意避免對一個object多次調用autorelease,對object調用一次autorelease將導致當前幀結束時對這個對象調用一次release,假設一個對象沒被添加進任何容器,則其引用計數值為1,如果調用兩次autorelease則當前幀結束時會調用兩次release其中第二次必定是在一個已經被銷毀的對象上執行的.而如果這一對象已經添加另外一個容器,則會導致那個容器在當前幀結束之后持有一個已經被銷毀的對象指針.此問題在cocos2dx rc3中已經添加了assert處理,對象銷毀時判斷在pool是否有另外的實例存在,如果有則報錯.雖然只在debug版本中處理,但也不用通過遍歷vector這么低效的手段來判斷重復實例的存在吧.難道2.2.3版本的m_uAutoReleaseCount就沒有給開發人員提供一點啟發?

我們來看看引擎的主循環:

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().

void CCPoolManager::pop()
{
    if (! m_pCurReleasePool)
    {
        return;
    }

     int nCount = m_pReleasePoolStack->count();

    m_pCurReleasePool->clear();

      if(nCount > 1)
      {
        m_pReleasePoolStack->removeObjectAtIndex(nCount-1);

//         if(nCount > 1)
//         {
//             m_pCurReleasePool = m_pReleasePoolStack->objectAtIndex(nCount - 2);
//             return;
//         }
        m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount - 2);
    }

    /*m_pCurReleasePool = NULL;*/
}

好了關鍵函數就是m_pCurReleasePool->clear(),這個函數完成了對象的清理過程.

在這里我們發現了一點奇怪的地方,為啥有個m_pReleasePoolStack還有個m_pCurReleasePool.

首先來看下ReleasePool對象是在哪里被創建的:

void CCPoolManager::push()
{
    //if(!m_pCurReleasePool){
        CCAutoreleasePool* pPool = new CCAutoreleasePool();       //ref = 1
        m_pCurReleasePool = pPool;

        m_pReleasePoolStack->addObject(pPool);                   //ref = 2

        pPool->release();                                       //ref = 1
    //}
}

然后我們跟蹤程序的運行將斷點放在push函數里,斷點的第一次命中調用棧如下:

 libcocos2d.dll!cocos2d::CCPoolManager::push() 行 145 C++
 libcocos2d.dll!cocos2d::CCPoolManager::getCurReleasePool() 行 200    C++
 libcocos2d.dll!cocos2d::CCPoolManager::addObject(cocos2d::CCObject * pObject=0x003e9938) 行 189  C++
 libcocos2d.dll!cocos2d::CCObject::autorelease() 行 100   C++
libcocos2d.dll!cocos2d::CCDictionary::create() 行 396    C++
 libcocos2d.dll!cocos2d::CCConfiguration::init() 行 60    C++
 libcocos2d.dll!cocos2d::CCConfiguration::sharedConfiguration() 行 154    C++
 libcocos2d.dll!cocos2d::CCDirector::setDefaultValues() 行 199    C++
 libcocos2d.dll!cocos2d::CCDirector::init() 行 113    C++
 libcocos2d.dll!cocos2d::CCDirector::sharedDirector() 行 97   C++
 IslandFight.exe!AppDelegate::applicationWillEnterForeground() 行 143 C++
 libcocos2d.dll!cocos2d::CCEGLView::WindowProc(unsigned int message=5, unsigned int wParam=0, long lParam=20972000) 行 444    C++
 libcocos2d.dll!cocos2d::_WindowProc(HWND__ * hWnd=0x002a06aa, unsigned int uMsg=5, unsigned int wParam=0, long lParam=20972000) 行 171   C++

第二次調用棧:

libcocos2d.dll!cocos2d::CCPoolManager::push() 行 145 C++
libcocos2d.dll!cocos2d::CCDirector::init() 行 165    C++
libcocos2d.dll!cocos2d::CCDirector::sharedDirector() 行 97   C++
IslandFight.exe!AppDelegate::applicationWillEnterForeground() 行 143 C++
libcocos2d.dll!cocos2d::CCEGLView::WindowProc(unsigned int message=5, unsigned int wParam=0, long lParam=20972000) 行 444    C++
libcocos2d.dll!cocos2d::_WindowProc(HWND__ * hWnd=0x002a06aa, unsigned int uMsg=5, unsigned int wParam=0, long lParam=20972000) 行 171   C++

也就是說總共創建了兩個ReleasePool,ReleasePool都被添加到m_pReleasePoolStack成員中,這個成員是一個arraym_pCurReleasePool則指向m_pReleasePoolStack的最后一個成員.

接着我們將斷點放到CCPoolManager::pop()中,可以發現第一次pop命中的時候nCount == 2,所以執行if(nCount > 1)之后的流程, 這個流程的處理就是將m_pReleasePoolStack的最后一個成員銷毀,然后將m_pCurReleasePool指向m_pReleasePoolStack中最后一個成員.

繼續觀察程序的運行可以發現,在第一幀之后m_pReleasePoolStack中實際上永遠只剩一個成員.我將

bool CCDirector::init(void)中的CCPoolManager::sharedPoolManager()->push()注釋掉,程序依舊能正確的運行.

我搜索cocos2dx 2.2.3和cocos2dx 3.0rc源代碼,並無找到必須啟用多個ReleasePool的地方, 那么多ReleasePool存在的意義就僅剩調試了.

研究cocos2dx不久,文中有理解不當的地方歡迎指正.

 





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM