天天看點

Cocos2d-x 3.2 的記憶體管理詳解

本文首先介紹 Cocos2d-x 3.2 中記憶體管理的作用,以及各個作用的應用。借由通俗易懂的解釋來了解記憶體管理的過程。其次通過源碼解析介紹其内部的實作原理。加深了解,進而在有需要的時候繞開引擎建立自己的記憶體管理機制。

一、Cocos2d-x 3.2 記憶體管理的兩個方面

1) 通過加入 

autorelease

 來自動釋放那些建立後未使用的對象。

2) 通過節點管理來保證對象在棄用後及時地删除。

1、及時釋放棄用的對象

使用條件:該對象是Node的子類對象

使用方法:addChild、removeChild

記憶體管理過程:

addChild //添加對象後,對象可以被使用
removeChild //删除對象後,對象被立刻删除(通過 delete) 
           

2、及時釋放未使用的對象

簡述:新建立的對象如果一幀内不使用,就會被自動釋放。(所謂一幀,即是一個gameloop。)

使用條件:對象通過

CREAT_FUNC()

宏建立或者對象使用

autorelease

加入了自動釋放池。

使用方法:自動實作

記憶體管理過程:

  • 對象不使用的情況
    對象建立 引用+1
    對象自動釋放 引用-1
               
  • 對象使用的情況
    對象建立 引用+
    對象使用 引用+ // 通過 addChild 使用對象
    對象自動釋放 引用-
               

引用的初始值為0,如果一幀結束後對象的引用值還是0,那就就會被 delete。

^^ 版權銜接線 vv      全文出處:http://www.cnblogs.com/tangyikejun/p/4361638.html

二、記憶體管理的實作原理

涉及記憶體管理的檔案很多,僅展示直接相關的部分代碼。

1、第一部分

Ref

 類: 進行引用計數、提供加入自動釋放池的接口。

AutoreleasePool

 類: 管理一個 

vector

 數組來存放加入自動釋放池的對象。提供對釋放池的清空操作。

PoolManager

 類: 管理一個 

vector

 數組來存放自動釋放池。預設情況下引擎隻建立一個自動釋放池,是以這個類是提供給開發者使用的,例如出于性能考慮添加自己的自動釋放池。

DisplayLinkDirector

 類: 這是一個導演類,提供遊戲的主循環,實作每一幀的資源釋放。這個類的名字看起來有點怪,但是不用管它。因為這個類繼承了 

Director

 類,也是唯一一個繼承了 

Director

 的類,也就是說完全可以合并為一個類,引擎開發者在源碼中有部分說明。

1.1 Ref

// 引用計數變量
unsigned int _referenceCount;

// 對象被構造後,引用計數值為 1
Ref::Ref()
: _referenceCount() //當Ref對象被建立時,引用計數的值為 1
{
#if CC_ENABLE_SCRIPT_BINDING
    static unsigned int uObjectCount = ;
    _luaID = ;
    _ID = ++uObjectCount;
    _scriptObject = nullptr;
#endif
    
#if CC_USE_MEM_LEAK_DETECTION
    trackRef(this);
#endif
}

// 引用+1
void Ref::retain()
{
    CCASSERT(_referenceCount > , "reference count should greater than 0");
    ++_referenceCount;
}

// 引用-1 。如果引用為0則釋放對象
void Ref::release()
{
    CCASSERT(_referenceCount > , "reference count should greater than 0");
    --_referenceCount;

    if (_referenceCount == )
    {

#if CC_USE_MEM_LEAK_DETECTION
        untrackRef(this);
#endif
        delete this; // 注意這裡 把對象 delete 了
    }
}

// 提供加入自動釋放池的接口。對象調用此函數即可加入自動釋放池的管理。
Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

//擷取引用計數值
unsigned int Ref::getReferenceCount() const
{
    return _referenceCount;
}
           

1.2 AutoreleasePool

// 存放釋放池對象的數組
std::vector<Ref*> _managedObjectArray;

// 往釋放池添加對象
void AutoreleasePool::addObject(Ref* object)
{
    _managedObjectArray.push_back(object);
}

// 清空釋放池,将其中的所有對象都 delete
void AutoreleasePool::clear()
{
    // 釋放所有對象
    for (const auto &obj : _managedObjectArray)
    {
        obj->release();
    }
    // 清空vector數組
    _managedObjectArray.clear();
}

// 檢視某個對象是否在釋放池中
bool AutoreleasePool::contains(Ref* object) const
{
    for (const auto& obj : _managedObjectArray)
    {
        if (obj == object)
            return true;
    }
    return false;
}
           

1.3 PoolManager

// 釋放池管理器單例對象
static PoolManager* s_singleInstance;

// 釋放池數組
std::vector<AutoreleasePool*> _releasePoolStack;

// 擷取 釋放池管理器的單例
PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        // 建立一個管理器對象
        s_singleInstance = new PoolManager(); 
        
        // 添加一個自動釋放池
        new AutoreleasePool("cocos2d autorelease pool");// 内部使用了釋放池管理器的push,這裡的調用很微妙,讀者可以動手看一看
    }
    return s_singleInstance;
}

// 擷取目前的釋放池
AutoreleasePool* PoolManager::getCurrentPool() const
{
    return _releasePoolStack.back();
}

// 檢視對象是否在某個釋放池内
bool PoolManager::isObjectInPools(Ref* obj) const
{
    for (const auto& pool : _releasePoolStack)
    {
        if (pool->contains(obj))
            return true;
    }
    return false;
}

// 添加釋放池對象
void PoolManager::push(AutoreleasePool *pool)
{
    _releasePoolStack.push_back(pool);
}

// 釋放池對象出棧
void PoolManager::pop()
{
    CC_ASSERT(!_releasePoolStack.empty());
    _releasePoolStack.pop_back();
}
           

1.4 DisplayLinkDirector

void DisplayLinkDirector::mainLoop()
{
    //第一次當導演
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();//進行清理工作
    }
    else if (! _invalid)
    {
        // 繪制場景,遊戲主要工作都在這裡完成
        drawScene();
     
        // 清空資源池
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}
           

根據目前的分析,我們先來捋一捋,待會兒再進一步深入。記憶體管理的過程是怎麼樣的呢?首先,建立了一個 

Node

 對象A,

Node

 繼承Ref,是以 

Ref

 的引用計數為1;然後,A通過 

autorelease

 将自己放入自動釋放池;

drawScene()

 完成後,一幀結束,

Director

 通過釋放池将池中的對象 

clear()

,即對 

Node

 對象A進行 

release()

 操作。A的引用計數變為0,執行 

delete

 釋放A對象。

接下來我們繼續介紹另外幾個與記憶體管理有關的類。

2、第二部分

Node

 類:提供了 

addChild

 和 

removeChild

 方法來建立遊戲的節點樹。

Vector

 類:封裝了對于對象的 

retain

 操作和 

release

 操作。

2.1 Node

// 添加節點
void Node::addChild(Node *child)
{
    CCASSERT( child != nullptr, "Argument must be non-nil");
    this->addChild(child, child->_localZOrder, child->_name); // 經過這個方法-->addChildHelper-->insertChild,完成retain操作
}

// 移除節點
void Node::removeChild(Node* child, bool cleanup /* = true */)
{
    //
    if (_children.empty())
    {
        return;
    }

    // 
    ssize_t index = _children.getIndex(child);
    if( index != CC_INVALID_INDEX )
        this->detachChild( child, index, cleanup );//注意這個函數
}

// 插入節點
void Node::insertChild(Node* child, int z)
{
    _transformUpdated = true;
    _reorderChildDirty = true;
    _children.pushBack(child);// pushBack方法對節點進行了retain
    child->_setLocalZOrder(z);
}


// 剝離節點
void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
{
    
    ...// 部分省略

    _children.erase(childIndex);// erase方法對節點進行了release
}
           

2.2 Vector

// 這裡僅展示與Node類相關的記憶體管理的部分

// 将對象入棧,引用+
void pushBack(T object)
{
    CCASSERT(object != nullptr, "The object should not be nullptr");
    _data.push_back( object );

    object->retain(); // 進行了retain
}

// 将目标位置的對象移除
iterator erase(ssize_t index)
{
    CCASSERT(!_data.empty() && index >= && index < size(), "Invalid index!");
    auto it = std::next( begin(), index );

    (*it)->release(); // 進行了release

    return _data.erase(it);
}
           

到這裡,終于可以把故事完整地講一遍了。記憶體管理的過程是怎麼樣的呢?首先,建立了一個 

Node

 對象A, 

Node

 繼承 

Ref

,是以 

Ref

 的引用計數為1;然後A又通過 

autorelease

 将自己放入自動釋放池;接着,有個 

Node

 對象B,B通過 

addChild(A)

 使得A的引用+1;幾個 

mainLoop

 後,B通過 

removeChild(A)

 使得A的引用-1;這個 

mainLoop

 的 

drawScene()

 完成後,一幀結束, 

Director

 通過釋放池将池中的對象 

clear()

,即對 

Node

 對象A進行 

release

 操作。A的引用計數變為0,執行 

delete

 釋放A對象。

3、高階用法

之是以稱為高階用法,是因為,如果開發者對 Cocos 的記憶體管理機制了解不夠深刻,那麼很可能會用錯而導緻損失大于收益。另一方面,這類用法在平時很少會用到。

3.1 使用 

retain

 來延長對象的生存時間

在開發過程中,如果需要使用一個節點對象,但是又不想把它放到節點樹裡面去。那麼就可以使用 

retain

 來避免對象被自動釋放掉。

3.2 使用 

PoolManager

 的 

push

 來延長對象的生存時間

有些情況下,希望閑置對象晚一幀進行銷毀,可以使用 

push

 把目前釋放池推入棧底,那麼這一幀結束的時候隻會釋放剛 

push

 進去的釋放池。

筆者本身還沒有機會使用過高階用法,如果有小夥伴發現了高階用法在實際問題中的應用,敬請留言交流。

源位址:http://www.cnblogs.com/tangyikejun/p/4361638.html?utm_source=tuicool

繼續閱讀