Cocos2d-x 3.1 內存管理機制

Cocos2d-x使用的內存管理方式是引用計數,引用計數是一種很有效的機制,通過給每個對象維護一個引用計數器,記錄該對象當前被引用的次數。當對象增加一次引用時,計數器加1;而對象失去一次引用時,計數器減1;當引用計數爲0時,標誌着該對象的生命週期結束,自動觸發對象的回收釋放。引用計數的重要規則是每一個程序片段必須負責任地維護引用計數,在需要維持對象生存的程序段的開始和結束分別增加和減少一次引用計數,這樣就可以實現十分靈活的內存管理。

接下來看一下Cocos2d-x 3.1 版本的源碼是怎麼實現引用計數的。


一、Ref

我們都知道幾乎每個類都繼承一個類Ref,打開CCRef.h查看Ref類,去掉其它與引用計數無關的,可以簡化爲:

// CCRef.h
class CC_DLL Ref
{
public:
    void retain();	// 引用計數加1
    void release();	// 引用計數減1
    Ref* autorelease();	// 將對象交給自動釋放池

    unsigned int getReferenceCount() const;	// 獲取當前的引用計數

protected:
    Ref();		// 構造函數,這裏的權限爲protected,說明自己不能實例化,子類可以實例化

public:
    virtual ~Ref();

protected:
    unsigned int _referenceCount;	// 引用計數

    friend class AutoreleasePool;	// 這個先別管
};
// CCRef.cpp
Ref::Ref(): _referenceCount(1) // new一個對象時,引用計數加1
{
}

Ref::~Ref()
{
}

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

void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should greater than 0");
    --_referenceCount;

    if (_referenceCount == 0)	// 引用爲0時說明沒有調用該對象,此時delete對象
    {
        delete this;
    }
}

Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);	// 交給自動釋放池管理
    return this;
}

unsigned int Ref::getReferenceCount() const
{
    return _referenceCount;
}


總結一下,Ref類就做了幾件事:

1、Ref自己不能實例化,只能由子類實例化;

2、創建是引用計數爲1;

3、調用retain引用計數加1;

4、調用release引用計數減1;

5、調用autorelease並沒有使引用計數減1,而是交給自動釋放池來管理。


那麼自動釋放池是什麼呢?肯定跟autorelease方法裏面的PoolManager有關。

打開CCAutoreleasePool.h文件查看,發現有兩個類,一個是AutoreleasePool,一個是PoolManager,從字面意思看,AutoreleasePool就是自動釋放池,而PoolManager就是池管理器,這些思路有點清晰了:

1、調用autorelease後對象交給AutoreleasePool來管理;

2、PoolManager是用來管理AutoreleasePool的,說明可以有多個池。



二、AutoreleasePool

接下來一步步看,先看AutoreleasePool自動釋放池,看簡化版本的:

// AutoreleasePool.h
class CC_DLL AutoreleasePool
{
public:
    AutoreleasePool();	// 疑問1:不要在堆上創建,而應該在棧上創建(爲什麼呢?等下解釋)
    ~AutoreleasePool();


    void addObject(Ref *object);	// 添加一個Ref對象到釋放池
    void clear();	// 清理自動釋放池
    
private:
    std::vector<Ref*> _managedObjectArray;	// 用來保存這個自動釋放池裏面加入的所有Ref對象
};
// AutoreleasePool.cpp
AutoreleasePool::AutoreleasePool()
{
    _managedObjectArray.reserve(150);		// 1、設置容器大小爲150
    PoolManager::getInstance()->push(this);	// 2、新建一個釋放池時就加入了釋放池管理器中(可以暫時放着,等看了PoolManager再回來看)
}

AutoreleasePool::~AutoreleasePool()
{
    clear();	// 1、清理釋放池

    PoolManager::getInstance()->pop();	// 2、將釋放池從釋放池管理器中刪除
}

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

void AutoreleasePool::clear()
{
    for (const auto &obj : _managedObjectArray)	// 1、調用所有自動釋放對象的release函數,注意:只有當引用計數爲0時纔會delete對象,相同對象加入幾次就會release幾次
    {
        obj->release();
    }
    _managedObjectArray.clear();	// 2、清除容器
}


總結一下,AutoreleasePool類就做了幾件事:

1、維持一個保存Ref對象的隊列,這些Ref對象調用autorelease就會加到該隊列,調用addObject函數添加;

2、clear函數對AutoreleasePool管理的所有Ref執行一次release操作,只有當引用計數爲0時對象纔會delete,加入幾次就執行幾次release操作。


三、PoolManager

PoolManager是管理釋放池的,在AutoreleasePool用到push和pop方法,可以猜到PoolManager應該維持一個存放釋放池的棧:

// PoolManager.h 
class CC_DLL PoolManager
{
public:
    static PoolManager* getInstance();	// PoolManager是個單例模式	
    
    static void destroyInstance();		// 刪除單例模式創建的對象
    
    AutoreleasePool *getCurrentPool() const;	// 獲取當前的釋放池
    
private:
    PoolManager();
    ~PoolManager();
    
    void push(AutoreleasePool *pool);	// 壓入一個釋放池
    void pop();							// 彈出一個釋放池
    
    static PoolManager* s_singleInstance;
    
    std::deque<AutoreleasePool*> _releasePoolStack;	// 存放自動釋放池的棧
    AutoreleasePool *_curReleasePool;	// 當前的自動釋放池
};
// PoolManager.cpp 
PoolManager* PoolManager::s_singleInstance = nullptr;

// 獲取單例模式時,如果還沒創建,則會創建兩個釋放池並添加到池管理器中
PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        s_singleInstance = new PoolManager();

		// 第一個池:AutoreleasePool構造函數會將構造的池添加到池管理器中
        s_singleInstance->_curReleasePool = new AutoreleasePool();
		// 第二個池:將new出來的釋放池再一次壓入池管理器中
        s_singleInstance->_releasePoolStack.push_back(s_singleInstance->_curReleasePool);
    }
    return s_singleInstance;
}

// delete單例模式創建的對象
void PoolManager::destroyInstance()	
{
    delete s_singleInstance;
    s_singleInstance = nullptr;
}

PoolManager::PoolManager()
{
}

// 析構函數
PoolManager::~PoolManager()
{
    while (!_releasePoolStack.empty())
    {
        AutoreleasePool* pool = _releasePoolStack.back();
        _releasePoolStack.pop_back();	// 1、彈出自動釋放池
        
        delete pool;	// 2、delete自動釋放池對象
    }
}

// 獲取當前釋放池
AutoreleasePool* PoolManager::getCurrentPool() const
{
    return _curReleasePool;
}

void PoolManager::push(AutoreleasePool *pool)
{
    _releasePoolStack.push_back(pool);	// 1、壓入釋放池
    _curReleasePool = pool;		// 2、設爲當前釋放池
}

// 彈出棧頂釋放池,並將當前釋放池指向新的棧頂釋放池
void PoolManager::pop()
{
    CC_ASSERT(_releasePoolStack.size() >= 1);
    
    _releasePoolStack.pop_back();
    
    if (_releasePoolStack.size() > 1)
    {
        _curReleasePool = _releasePoolStack.back();
    }
}


貌似PoolManager功能更加簡單,就是管理釋放池。



四、內存管理思路

1、autorelease

new一個對象的時候,調用其autorelease將對象交給自動釋放池管理

Ref* Ref::autorelease()
{
	PoolManager::getInstance()->getCurReleasePool()->addObject(this);
	return this;
}

2、PoolManager::getInstance()

在獲取單例對象時,如果不存在則會創建一個PoolManager對象,這時候會添加兩個釋放池



引擎自己會維持兩個默認的釋放池,如果我們沒有手動創建釋放池,則autorelease對象都添加到棧頂默認釋放池。

其實我還沒弄懂這裏爲什麼要有兩個默認的釋放池,一個也可以的。


3、getCurReleasePool()獲取的是當前釋放池,addObject()將Ref對象加入當前釋放池中。

void AutoreleasePool::addObject(Ref* object)
{
    _managedObjectArray.push_back(object);
}
這樣,每一個調用autorelease的Ref對象都會添加到_managedObjectArray中。


4、自動釋放池的對象是怎麼釋放的?看AutoreleasePool:clear()函數


這裏循環_managedObjectArray調用裏面對象的release,調用1次release,引用計數就減1,當引用計數爲0時就delete該對象。

你肯定很疑惑,在哪裏會調用clear函數呢,~AutoreleasePool()會調用,但是那是在delete的時候釋放的,我們看到Director類的主循環:


看到這裏就明白了吧,每一次主循環都會調用clear來release自動釋放池的對象,而每一幀會執行一次主循環,也就是每一幀都會清除一次。



五、手動創建釋放池

我們已經知道,調用了autorelease()方法的對象將會在自動釋放池池釋放的時候被釋放一次。雖然Cocos2d-x已經保證每一幀結束後釋放一次釋放池,但是如果在一幀之內生成了大量的autorelease對象,將會導致釋放池性能下降。因此在生存autorelease對象密集的區域(通常是循環中)的前後,最後手動創建一個釋放池。

{
	AutoreleasePool pool1;	// 手動創建一個釋放池		
	for ()
	{	
		ref->autorelease();	// 循環裏面執行autorelease,這些對象會添加到pool1中
	}
}

此時,引擎維護三個釋放池,我們知道每一幀結束時會執行當前釋放池的clear(),所以上面的那些對象就會在第一幀結束時被釋放,而那些放在引擎默認釋放池的autorelease對象就會在下一幀被釋放,錯開了釋放的時間,這樣就不會降低釋放池的性能。


看到上面的代碼,你會感到疑惑:爲什麼只有創建釋放池,而沒有釋放。還記得在AutoreleasePool.h中AutoreleasePool構造函數的註釋嗎:不要在堆上創建,而應該在棧上。我們知道,new出來對象必須手動delete才能釋放,而在棧上的變量,當作用域消失就會釋放,如上面的pool1,當執行到最後一個“}”時就會調用其析構函數,看看AutoreleasePool構造和析構函數做了些什麼:


創建一個AutoreleasePool是會被push到PoolManager中,而作用域系消失時就會執行析構函數,調用pop從PoolManager中刪除該釋放池。

這樣,在這個局部作用域pool1之間所有的內存管理實際上是交給了AutoreleasePool來完成,真的是好方法。



如果上面哪裏說錯了,可以指出來大家討論討論。網上有很多關於Cocos2d-x內存管理的教程了,大家如果覺得我的很難理解的話可以查考下下面幾篇精華:

1、內存管理源碼分析http://cn.cocos2d-x.org/tutorial/show?id=850

2、Cocos2d-x內存管理淺說 http://www.cocoachina.com/newbie/basic/2013/0528/6290.html

3、Cocos2d-x內存管理的一種實現 http://www.cocoachina.com/applenews/devnews/2013/0531/6315.html

4、深入理解Cocos2d-x內存管理 http://www.cocoachina.com/applenews/devnews/2013/0621/6455.html






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