CCObject的分析:release、retain 基於2.2.3,增加3.2 ref對比,ccobjectretain

來源:互聯網
上載者:User

CCObject的分析:release、retain 基於2.2.3,增加3.2 ref對比,ccobjectretain

CCSprite * fish = new CCSprite;CCLOG("After new: %d",fish->retainCount());fish->init();CCLOG("After init: %d",fish->retainCount());fish->retain();CCLOG("After retain: %d",fish->retainCount());fish->release();CCLOG("After release: %d",fish->retainCount());fish->autorelease();CCLOG("After autorelease: %d",fish->retainCount()); //實際操作了+1-1.


結果:

After new: 1
After init: 1
After retain: 2
After release: 1
After autorelease: 1
“HelloCpp.exe”(Win32): 已載入“C:\WINDOWS\SysWOW64\SogouTSF.ime”。無法尋找或開啟 PDB 檔案。


class CC_DLL CCCopying{public:    virtual CCObject* copyWithZone(CCZone* pZone);};/** * @js NA */class CC_DLL CCObject : public CCCopying{public:    // object id, CCScriptSupport need public m_uID    unsigned int        m_uID;    // Lua reference id    int                 m_nLuaID;protected:    // count of references    unsigned int        m_uReference;    // count of autorelease    unsigned int        m_uAutoReleaseCount;public:    CCObject(void);    /**     *  @lua NA     */    virtual ~CCObject(void);        void release(void);    void retain(void);    CCObject* autorelease(void);    CCObject* copy(void);    bool isSingleReference(void) const;    unsigned int retainCount(void) const;    virtual bool isEqual(const CCObject* pObject);    virtual void acceptVisitor(CCDataVisitor &visitor);    virtual void update(float dt) {CC_UNUSED_PARAM(dt);};        friend class CCAutoreleasePool;};

如下:預設初始化reference為1,

CCObject::CCObject(void): m_nLuaID(0), m_uReference(1) // when the object is created, the reference count of it is 1, m_uAutoReleaseCount(0) // {    static unsigned int uObjectCount = 0;    m_uID = ++uObjectCount;}

看看autorelease的源碼就知道實現了+1和-1的操作:

CCObject* CCObject::autorelease(void){    CCPoolManager::sharedPoolManager()->addObject(this);    return this;}
//<span style="font-family: Arial, Helvetica, sans-serif;">CCAutoreleasePool.cpp</span>
void CCPoolManager::addObject(CCObject* pObject){    getCurReleasePool()->addObject(pObject);}
void CCAutoreleasePool::addObject(CCObject* pObject){    m_pManagedObjectArray->addObject(pObject); //赫然使用了CCArray。實現了+1的,隨著3.2使用C++11文法後使用vector去管理就沒有這麼糾結了。    CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");    ++(pObject->m_uAutoReleaseCount); //同時在當前的pool中增加管理    pObject->release(); // no ref count, in this case autorelease pool added.   //必須-1,否則泄露。}



每個實現單例的類都應該提供清除操作:如下所示,以purge開頭。

void CCPoolManager::purgePoolManager(){    CC_SAFE_DELETE(s_pPoolManager);}


在2.2系列中,每個CCAutoreleasePool實際上就是一個CCArray,儲存了一系列CCObject*,通過m_uAutoReleaseCount來計數。
void CCAutoreleasePool::clear(){    if(m_pManagedObjectArray->count() > 0)    {        //CCAutoreleasePool* pReleasePool;#ifdef _DEBUG        int nIndex = m_pManagedObjectArray->count() - 1;#endif        CCObject* pObj = NULL;        CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)        {            if(!pObj)                break;            --(pObj->m_uAutoReleaseCount);            //(*it)->release();            //delete (*it);#ifdef _DEBUG            nIndex--;#endif        }        m_pManagedObjectArray->removeAllObjects();    }}



回顧一下CCDiretor的初始化源碼段:

    // scheduler    m_pScheduler = new CCScheduler(); // 計時器    // action manager    m_pActionManager = new CCActionManager(); //動作管理器    m_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false);    // touchDispatcher    m_pTouchDispatcher = new CCTouchDispatcher();  //觸摸訊號管理器    m_pTouchDispatcher->init();    // KeypadDispatcher    m_pKeypadDispatcher = new CCKeypadDispatcher(); //鍵盤訊號管理器    // Accelerometer    m_pAccelerometer = new CCAccelerometer(); // 加速器管理器    // create autorelease pool    CCPoolManager::sharedPoolManager()->push(); //建立一個當前的pool並加入PoolManager中,還是一個CCArray,名為:m_pReleasePoolStack,實際上觸控更換為vector是從2.0的版本左右就計劃好了。2.x版本是個過渡,也就是說,瞭解了2.x,更具版本更新說明,3.x不存在代溝。
通告層NotificationNode使用說明

下面有個NotificationNode,實際上和Scene屬於UI種類,因為是後繪製,會遮蓋當前scene,用途在於loading或者提示這種彈窗,記得觸控螢幕蔽的相關設定

// Draw the Scenevoid CCDirector::drawScene(void){    // calculate "global" dt    calculateDeltaTime();    //tick before glClear: issue #533    if (! m_bPaused)    {        m_pScheduler->update(m_fDeltaTime);    }    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    /* to avoid flickr, nextScene MUST be here: after tick and before draw.     XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */    if (m_pNextScene)    {        setNextScene();    }    kmGLPushMatrix();    // draw the scene    if (m_pRunningScene)    {        m_pRunningScene->visit();    }    // draw the notifications node    if (m_pNotificationNode)    {        m_pNotificationNode->visit();    }        if (m_bDisplayStats)    {        showStats();    }        kmGLPopMatrix();    m_uTotalFrames++;    // swap buffers    if (m_pobOpenGLView)    {        m_pobOpenGLView->swapBuffers();    }        if (m_bDisplayStats)    {        calculateMPF();    }}

原以為會是在drawscene內調用,誰知不是。這是個題外話,無關緊要的。


迴歸正題:

在經過CCDisplayLinkDirector繼承CCDirector,在mainLoop中調用poolManager,這個才是真正的cocos程式迴圈,程式入口的是win32或者ios的主程式迴圈:

簡化版:

int CCApplication::run(){    <span style="white-space:pre">// Initialize instance and cocos2d.    if (!applicationDidFinishLaunching())    {        return 0;    }    CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();    pMainWnd->centerWindow();    ShowWindow(pMainWnd->getHWnd(), SW_SHOW);    while (1)    {        if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))        {            // Get current time tick.            QueryPerformanceCounter(&nNow);            // If it's the time to draw next frame, draw it, else sleep a while.            if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart)            {                nLast.QuadPart = nNow.QuadPart;                CCDirector::sharedDirector()->mainLoop();            }            else            {                Sleep(0);            }            continue;        }        if (WM_QUIT == msg.message)        {            // Quit message loop.            break;        }        // Deal with windows message.        if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))        {            TranslateMessage(&msg);            DispatchMessage(&msg);        }    }</span>    }


void CCDisplayLinkDirector::mainLoop(void){    if (m_bPurgeDirecotorInNextLoop) /*雖然cocos2d允許多個director,準備相容多視窗程序(實際上到目前3.2為止,cocos2d應用多為是單視窗,只有一個視窗控制代碼,支援多情境、層,支援C++11多線程的引擎 */    {//導演類清理自己        m_bPurgeDirecotorInNextLoop = false;        purgeDirector();    }    else if (! m_bInvalid)     {         drawScene();              // release the objects         CCPoolManager::sharedPoolManager()->pop();             }}


實際上終於回到了我們要密切關注的CCObject記憶體回收機制了:

每次pop()都去調用當前CCAutoreleasePool的clear操作,因為原理在於Director將遊戲世界切片為每一幀,並為每一幀設定繪製時間長度,當GPU和CPU在裝置上完成上一幀的製作,會計算一個時間長度,長度不夠時會佔用當前幀的可用時間長度,當前幀繪製的必要時間長度於剩餘幀時間長度時跳過當前幀的CPU執行和GPU繪製,這就是為什麼掉幀和碰撞穿透的原因,而在clear操作中,代碼如下:

void CCAutoreleasePool::clear(){    if(m_pManagedObjectArray->count() > 0)    {        //CCAutoreleasePool* pReleasePool;#ifdef _DEBUG        int nIndex = m_pManagedObjectArray->count() - 1;#endif        CCObject* pObj = NULL;        CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)        {            if(!pObj)                break;            --(pObj->m_uAutoReleaseCount);            //(*it)->release();            //delete (*it);#ifdef _DEBUG            nIndex--;#endif        }        m_pManagedObjectArray->removeAllObjects();    }}

將當前幀的資料清理掉,m_uAutoReleaseCount變成0,但是仍未真正釋放記憶體。這時候記憶體管理機制就依靠m_uReference來辨別是否需要清理。所有只使用addChild而不retain的都會隨著父節點被清理掉。一方面我們可以自己pushPool,另一方面可以使用掛載在m_pobScenesStack中當前scene下。poolManager並不管理m_pobScenesStack主線的節點,只管理掉落在當前pool中通過autorelease增加至pool的CCObject。


3.2 ref對比待續

ref 內部變化:變得清爽多了,除了增加了 CC_USE_MEM_LEAK_DETECTION 先行編譯模式下的記憶體跟蹤:(這兩個不是ref內建函式)

static void trackRef(Ref* ref);
static void untrackRef(Ref* ref);

外,減少了原先CCObject中的各種引用,只剩下:_referenceCount,無論lua還是js、c++,公用一個引用計數,C++分支真的往庫或者大型3D多線程發展啦。只要堅持了過渡期,學好python、Linux的相關編譯,手遊純3D的可行性很高。

下面才是新增的記憶體追蹤顯示函數:

#if CC_USE_MEM_LEAK_DETECTIONpublic:    static void printLeaks();#endif

新的AutoreleasePool已經使用:std::vector<Ref*> _managedObjectArray; 標準C++作為容器,通過ref.autorelease()調用的代碼中不在進行+-1啦,

void AutoreleasePool::addObject(Ref* object){    _managedObjectArray.push_back(object);}

同時在mainLopp中變成了直接調用Autorelease的clear()操作:

void DisplayLinkDirector::mainLoop(){    if (_purgeDirectorInNextLoop)    {        _purgeDirectorInNextLoop = false;        purgeDirector();    }    else if (! _invalid)    {        drawScene();             // release the objects        PoolManager::getInstance()->getCurrentPool()->clear();    }}


3.2中

void Director::drawScene(){    // calculate "global" dt    calculateDeltaTime();        // skip one flame when _deltaTime equal to zero.    if(_deltaTime < FLT_EPSILON)    {        return;    }    if (_openGLView)    {        _openGLView->pollInputEvents();    }    //tick before glClear: issue #533    if (! _paused)    {        _scheduler->update(_deltaTime);        _eventDispatcher->dispatchEvent(_eventAfterUpdate);    }    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    /* to avoid flickr, nextScene MUST be here: after tick and before draw.     XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */    if (_nextScene)    {        setNextScene();    }    pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);    // draw the scene    if (_runningScene)    {        _runningScene->visit(_renderer, Mat4::IDENTITY, false);        _eventDispatcher->dispatchEvent(_eventAfterVisit);    }    // draw the notifications node    if (_notificationNode)    {        _notificationNode->visit(_renderer, Mat4::IDENTITY, false);    }    if (_displayStats)    {        showStats();    }    _renderer->render();    _eventDispatcher->dispatchEvent(_eventAfterDraw);    popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);    _totalFrames++;    // swap buffers    if (_openGLView)    {        _openGLView->swapBuffers();    }    if (_displayStats)    {        calculateMPF();    }}
通過調用setNextScene();來在每次繪製之前增加scene的引用計數,這樣子在drawscene之後調用clear就能清楚了無用的記憶體ref對象:

void Director::setNextScene(){    bool runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene) != nullptr;    bool newIsTransition = dynamic_cast<TransitionScene*>(_nextScene) != nullptr;    // If it is not a transition, call onExit/cleanup     if (! newIsTransition)     {         if (_runningScene)         {             _runningScene->onExitTransitionDidStart();             _runningScene->onExit();         }          // issue #709. the root node (scene) should receive the cleanup message too         // otherwise it might be leaked.         if (_sendCleanupToScene && _runningScene)         {             _runningScene->cleanup();         }     }    if (_runningScene)    {        _runningScene->release();    }    _runningScene = _nextScene;    _nextScene->retain();    _nextScene = nullptr;    if ((! runningIsTransition) && _runningScene)    {        _runningScene->onEnter();        _runningScene->onEnterTransitionDidFinish();    }}

同時看一下Node::addChild的操作,能解決我們的疑惑,使用了addChild和Autorelease兩種記憶體管理的區別:

void Node::addChild(Node* child, int localZOrder, const std::string &name){    CCASSERT(child != nullptr, "Argument must be non-nil");    CCASSERT(child->_parent == nullptr, "child already added. It can't be added again");        addChildHelper(child, localZOrder, INVALID_TAG, name, false);}
void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag){    if (_children.empty())    {        this->childrenAlloc();    }        this->insertChild(child, localZOrder);        if (setTag)        child->setTag(tag);    else        child->setName(name);        child->setParent(this);    child->setOrderOfArrival(s_globalOrderOfArrival++);    #if CC_USE_PHYSICS    // Recursive add children with which have physics body.    Scene* scene = this->getScene();    if (scene != nullptr && scene->getPhysicsWorld() != nullptr)    {        child->updatePhysicsBodyTransform(scene);        scene->addChildToPhysicsWorld(child);    }#endif        if( _running )    {        child->onEnter();        // prevent onEnterTransitionDidFinish to be called twice when a node is added in onEnter        if (_isTransitionFinished) {            child->onEnterTransitionDidFinish();        }    }        if (_cascadeColorEnabled)    {        updateCascadeColor();    }        if (_cascadeOpacityEnabled)    {        updateCascadeOpacity();    }}
這裡要注意了,addChild中用到的vector不是std的vector,是CCVector,cocos2d重寫的,

    void pushBack(T object)    {        CCASSERT(object != nullptr, "The object should not be nullptr");        _data.push_back( object );        object->retain();    }


所以實際上我們看似使用AddChild交給cocos2d的渲染樹協助我們管理記憶體和啟用AutoreleasePool形式管理記憶體是不相干,實際就相當於在以pool為載體的地區內建立了一批對象,根據需要去retian,或者掛載了渲染樹上(每棵渲染樹就是以scene為起點,addChild執行一次retian),每次渲染結束後都會清理一次記憶體池。所有在new之後,不調用Autorelease就要我們手動採取C++標準方式去管理記憶體;如果啟用Autorelease的機制,因為調用Autorelease實在drawscene(cpu遊戲主線和GPU渲染)之後,沒有了之前存在可能申請了馬上被清理的可能,所以不需要+-1,只需要在clear的時候判斷是否為0。AddChild的區別在於掛載在渲染樹上的節點_referenceCount初始為2,在執行一次clear之後就不在屬於PoolManager的當中的AutoReleasePool,而是只屬於渲染樹啦。

渲染樹的清理:

void Director::replaceScene(Scene *scene){    CCASSERT(_runningScene, "Use runWithScene: instead to start the director");    CCASSERT(scene != nullptr, "the scene should not be null");        if (scene == _nextScene)        return;        if (_nextScene)    {        if (_nextScene->isRunning())        {            _nextScene->onExit();        }        _nextScene->cleanup();        _nextScene = nullptr;    }    ssize_t index = _scenesStack.size();    _sendCleanupToScene = true;    _scenesStack.replace(index - 1, scene);    _nextScene = scene;}

 _nextScene->cleanup();
調用的是Node的cleanup,遞迴形式完成渲染樹的清理。


提示: //利用靜態函數實現每個pool建立自動添加至Manager,用的真妙!!C++只會越用越熟啊。

PoolManager* PoolManager::getInstance(){    if (s_singleInstance == nullptr)    {        s_singleInstance = new PoolManager();        // Add the first auto release pool         new AutoreleasePool("cocos2d autorelease pool");    }    return s_singleInstance;}
AutoreleasePool::AutoreleasePool(const std::string &name): _name(name)#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0), _isClearing(false)#endif{    _managedObjectArray.reserve(150);    PoolManager::getInstance()->push(this);}


所以每次執行完clear,Manager中除了渲染樹和_referenceCount>1都會被清理掉;可是pool被清理之後_referenceCount>=1的記憶體就如渲染樹般泄露了,這個時候就需要我用自己release(delete)了。為什麼會出現這樣子的情況呢,實際上源於早前相容ObjectC的代碼繼承過來的管理機制,可能之後Pool和Autorelease的機制會被清理調用,只儲存渲染樹和手動retian、release。這和之後啟用C++11 shared_ptr<typename>有關。因為3.2的pool實際上可有可無啦,完全可以讓程式去實現了記憶體管理。

至於CCCopying被抽離成CloneAble,ref沒有繼承了clone介面,就不分析啦。可惜目前我仍然無法搭建git環境,我最怕搭建環境了,比如花了4天去配置cocos2dx 2.x 和quick的win安卓環境,3天安裝mac虛擬機器,後來發現3.x完全是方便至極。 


下面是源碼推薦的用法:

// Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.            // This happens when 'autorelease/release' were not used in pairs with 'new/retain'.            //            // Wrong usage (1):            //            // auto obj = Node::create();   // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool.            // obj->autorelease();   // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first.            //            // Wrong usage (2):            //            // auto obj = Node::create();            // obj->release();   // Wrong: obj is an autorelease Ref, it will be released when clearing current pool.            //            // Correct usage (1):            //            // auto obj = Node::create();            //                     |-   new Node();     // `new` is the pair of the `autorelease` of next line            //                     |-   autorelease();  // The pair of `new Node`.            //            // obj->retain();            // obj->autorelease();  // This `autorelease` is the pair of `retain` of previous line.            //            // Correct usage (2):            //            // auto obj = Node::create();            // obj->retain();            // obj->release();   // This `release` is the pair of `retain` of previous line.





聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.