標籤:cocos2d-x 記憶體管理 引用計數 3.1
Cocos2d-x使用的記憶體管理方式是引用計數,引用計數是一種很有效機制,通過給每個對象維護一個引用計數器,記錄該對象當前被引用的次數。當對象增加一次引用時,計數器加1;而對象失去一次引用時,計數器減1;當引用計數為0時,標誌著該對象的生命週期結束,自動觸發對象的回收釋放。引用計數的重要規則是每一個程式片段必須負責任地維護引用計數,在需要維持對象生存的程式段的開始和結束分別增加和減少一次引用計數,這樣就可以實現十分靈活的記憶體管理。
接下來看一下Cocos2d-x 3.1 版本的源碼是怎麼實現引用計數的。
一、Ref
我們都知道幾乎每個類都繼承一個類Ref,開啟CCRef.h查看Ref類,去掉其它與引用計數無關的,可以簡化為:
// CCRef.hclass 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.cppRef::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.hclass CC_DLL AutoreleasePool{public: AutoreleasePool();// 疑問1:不要在堆上建立,而應該在棧上建立(為什麼呢?等下解釋) ~AutoreleasePool(); void addObject(Ref *object);// 添加一個Ref對象到釋放池 void clear();// 清理自動釋放池 private: std::vector<Ref*> _managedObjectArray;// 用來儲存這個自動釋放池裡面加入的所有Ref對象};
// AutoreleasePool.cppAutoreleasePool::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