天天看點

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對象,這時候會添加兩個釋放池

Cocos2d-x 3.1 記憶體管理機制
Cocos2d-x 3.1 記憶體管理機制

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

其實我還沒弄懂這裡為什麼要有兩個預設的釋放池,一個也可以的。

3、getCurReleasePool()擷取的是目前釋放池,addObject()将Ref對象加入目前釋放池中。

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

這樣,每一個調用autorelease的Ref對象都會添加到_managedObjectArray中。

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

Cocos2d-x 3.1 記憶體管理機制
Cocos2d-x 3.1 記憶體管理機制

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

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

Cocos2d-x 3.1 記憶體管理機制

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

五、手動建立釋放池

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

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

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

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

Cocos2d-x 3.1 記憶體管理機制

建立一個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

繼續閱讀