小水滴所理解的Cocos2dx內存管理機制

前言Cocos2dx採用的源於Objective-c語言的引用計數機制來進行內存管理。如果接觸過objective-c語言,那麼引用計數機制就好理解了。在使用C++語言編程的時候,用new運算符爲對象分配一塊內存,當該對象不需要被使用的時候,用delete運算符釋放掉該對象佔用的內存。這樣看起來挺不錯的,爲什麼Cocos2dx還要採用引用計數機制呢?這是因爲在開發過程中我們很容易犯一些與指針有關的錯誤,比如該釋放內存卻忘記釋放內存,造成內存泄漏;然後有時候我們釋放了ptr指向的那塊內存,卻沒有將ptr置空,在不經意之間有可能會再一次釋放ptr指向的內存,造成一些內存錯誤。那麼我們就希望有這麼一種機制的出現,讓我們不需要花費太多的精力去想在什麼時候去delete對象,所以Cocos2dx就採用了引用計數機制,可以很好的避免這樣一些問題。

簡述:Cocos2dx中有一個專門負責內存管理的類——Ref(後面會介紹它的源碼),大部分的類都會繼承這個基類。例如CCLayer,CCSprite,CCNode,CCScene

這樣繼承Ref的對象就會有一個引用計數(就是一個數字),當我們用new運算符構造一個繼承Ref的對象的時候,會將引用計數初始化爲1,調用Ref中的retain()方法使引用計數增加1,調用release()方法使引用計數減1,當引用計數爲0的時候就會用delete運算符幹掉該對象

Cocos2dx內存管理:

Sprite* sprite=Sprite::create("helloworld.png");
layer->addchild(sprite);
上面創建了一個Sprite對象,然後把這個對象加入到layer中,sprite就顯示出來了,我們也不再需要關心什麼時候去釋放sprite了,sprite對象的生命週期與layer生命週期關聯起來了,當layer被釋放的時候,sprite也會跟着被釋放。那麼這是爲什麼呢?下面我們跟進到Sprite::create("")源碼中去看看究竟。

Sprite* Sprite::create(const std::string& filename)
{
    Sprite *sprite = new (std::nothrow) Sprite();
    if (sprite && sprite->initWithFile(filename))
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return nullptr;
}
我們可以從上面源碼中看出來,首先new了一個Sprite對象,然後對該對象作了一個sprite->autorelease()操作,從命名上可以看出sprite會被自動釋放。那麼爲什麼sprite對象調用了autorelease方法之後就可以自動管理內存了呢?這是因爲Sprite類繼承了Ref類(絕大多數類都繼承了Ref),而這個autorelease方法就是Ref中的成員方法。繼續跟進到Ref源碼中。

class  Ref
{
public:
    /**
     * 引用計數加1
     */
    void retain();
    /**
     * 引用計數減1,減完之後如果引用計數爲0,則delete ptr
     */
    void release();
    /**
     * 將對象加入到autoReleasePool中,在每一幀的最後會對當前autoReleasePool中的每一個對象調用release()方法
     */
    Ref* autorelease();
    /**
     *  get方法,獲取引用計數
     */
    unsigned int getReferenceCount() const;
protected:
    /**
     * 構造函數,將構造函數屬性置爲protected,Ref無法被實例化,必須被繼承才能起作用。
     */
    Ref();
public:
    virtual ~Ref();
protected:
    // 引用計數
    unsigned int _referenceCount;
    friend class AutoreleasePool;
};
Ref類就是用來管理引用計數的,沒有其他功能。一個繼承自Ref類的對象調用retain()方法,引用計數加1;調用release()方法,引用計數減1;但是調用autoRelease()方法只是將指向該對象的指針加入到一個自動釋放池中,並不會改變該對象的引用計數。

看完上述源碼之後,我們大概就明白一些了,當new一個Sprite對象的時候,該對象的引用計數會被初始化爲1,然後調用autoRelease()方法將對象加入到自動釋放池中就ok了。再思考一個問題:當我們Sprite* sprite=Sprite::create("helloworld.png")創建一個Sprite對象,然後什麼也不做,這個時候sprite的引用計數始終是1嗎?該對象釋放的時機在哪?事實上當sprite調用了autoRelease()方法後,引用計數會在每一幀的最後減1。下面接着分析。

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

mainLoop()執行一次就是一幀,每幀的結尾都會調用一個PoolManager::getInstance()->getCurrentPool()->clear()方法,接着跟進一下clear方法。

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = true;
#endif
    for (const auto &obj : _managedObjectArray)
    {
        obj->release();
    }
    _managedObjectArray.clear();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = false;
#endif
}


從源碼中可以看出,遍歷一遍_managerdObjectArray,對每個obj調用release()方法,使得引用計數減1,減完之後如果引用計數爲0,就delete掉該對象。在clear的方法最後還會清理一遍_managedObjectArray。

我們最後再來梳理一遍最先的例子:

幀開始:

1. Sprite* sprite=Sprite::create("helloworld.png");      引用計數爲1

2. layer->addChild(sprite);    引用計數爲2    addChild()方法是Node中的成員方法,會增加sprite的引用計數。

幀結束:       引用計數爲1    在幀結束之前有操作:PoolManager::getInstance()->getCurrentPool()->clear()。

幀結束的時候,sprite的引用計數爲1,所以sprite會一直存在於內存中直到它的父類layer被釋放。那麼layer被釋放的時候做了什麼操作會使得sprite的引用計數減1呢?

在Node析構的時候,會遍歷一遍Node中的_children數組,調用_children中的每一個對象的release方法,sprite的引用計數減1,最終被析構。


寫的第一篇Cocos2dx文章,文章中有什麼問題或者不足之處希望大家提出來,共同進步。微笑






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章