設想如下場景,這是一個典型的內存合理分配的場景:在一幀內,有若干個函數,每個函數都會創建一系列的精靈,每個精靈都不同,都會佔用一定的內存,精靈的總數可能會有1000個,而一個函數只會創建10個精靈這樣,創建的精靈只會在這個函數中使用,大致代碼如下:
for(int i = 0; i < 10; i++)
{
Sprite* s = Sprite::create();
//-- doSomething --
}
這樣做會造成內存泄露嗎?
答案是當然不會,但是這樣會造成一幀內的內存峯值過高,因爲在引擎的自動內存管理中,所有的釋放內存操作都是在每一幀的結束纔會進行的,所以就算申請的內存在這一幀中不會有其他地方會使用,它的內存也不會隨着作用域的結束而釋放的。
那麼我們應該如何優化這段代碼呢?如下
AutoreleasePool pool;
for (int i = 0; i < 10; i++)
{
Sprite* s = Sprite::create();
//-- doSomething --
}
只需要在函數的第一句加上
AutoreleasePool pool;
就可以實現在函數結束的時候自動將create的指針釋放了,那麼爲什麼會有那麼神奇的效果呢?我們來分析一下這個函數的構造函數以及析構函數,首先分析一下構造函數:
AutoreleasePool::AutoreleasePool()
: _name("")
{
_managedObjectArray.reserve(150);
PoolManager::getInstance()->push(this);
}
他向PoolManager的單例中push了自己,我們進入push中看看它的具體實現
void PoolManager::push(AutoreleasePool *pool)
{
_releasePoolStack.push_back(pool);
}
直接向_releasePoolStack棧中壓入了this,那這個將會起到什麼效果呢?這就不得不說一下autorelease的實現了,衆所周知,create函數的內存自動管理機制依賴於autorelease函數,那麼autorelease函數是幹嘛用的呢:
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
向某個Pool池添加對象,那麼getCurrentPool獲取的是那個內存管理池呢?
AutoreleasePool* PoolManager::getCurrentPool() const
{
return _releasePoolStack.back();
}
就是最後我們通過push添加進來的那個池子,所以每創建一個AutoreleasePool 對象,都會壓入PoolManager中。然後後續的autorelease操作是將對象加入到最新創建的AutoreleasePool 對象中。
那麼最終要通過什麼途徑讓函數在函數結束的時候自動釋放內存呢?我們知道,作用域結束的時候,會調用普通對象的析構函數,那麼就來看看AutoreleasePool 的析構函數執行了什麼吧
AutoreleasePool::~AutoreleasePool()
{
clear();
PoolManager::getInstance()->pop();
}
第一個是clear函數,這是一個非常關鍵的函數,我們跟蹤進去
void AutoreleasePool::clear()
{
for (const auto &obj : _managedObjectArray)
{
obj->release();
}
_managedObjectArray.clear();
}
它會將所有addObject的對象全部執行一次release操作。這樣就可以實現在函數結束的時候自動釋放內存了。
PoolManager::getInstance()->pop();
這一行代碼主要是從PoolManager中將當前AutoreleasePool 對象彈出棧(因爲當前對象已經析構了)
上述就是通過使用AutoreleasePool 來合理的管理內存了