Why?
內存管理一直是個很重要的事情绝页,需要考慮分配和回收的時機娃惯,千萬不能出現(xiàn)內存泄漏,因為手機上內存本來就有限坠陈。內存管理的核心就是動態(tài)分配的內存及時回收。cocos2dx是從cocos2d-iphone發(fā)展過來的捐康,使用的引用計數(shù)的機制進行管理仇矾。了解了cocos中的內存管理機制,就可以明確的了解在什么時候retian在什么時候release解总。我盡量只說關鍵的地方贮匕,不貼大量的代碼。
引用計數(shù)
剛開始了解cocos的時候花枫,看了一些書刻盐,書上說cocos中是使用的引用計數(shù)的機制進行管理內存。當時想了下劳翰,很好理解啊敦锌,每一幀去檢查一下每個對象的引用計數(shù),如果是0則釋放掉佳簸。最近深入看了一下源碼乙墙,發(fā)現(xiàn)不是每幀去檢查每個對象,每個對象只會被檢查一次生均,后面就在恰當?shù)臅r機進行釋放听想。
自動釋放池
AutoreleasePool里面就是封裝了一個vector,并對vector中的對象進行管理马胧。
自動釋放池管理
PoolManager汉买,此類是對自動釋放池的管理,看到有這個管理類佩脊,就知道自動釋放池可能不止一個蛙粘,是的,自動釋放池可以創(chuàng)建多個威彰,都由PoolManager這個類管理出牧。PoolManager使用棧的操作來管理自動釋放池。
Ref類
此類中有個變量保存引用計數(shù)抱冷,繼承此類崔列,就可以很方便的加入自動釋放池中管理。引用計數(shù)初始的值為1
Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
一個Node被管理的過程
1:創(chuàng)建一個node
Node* node = Node::create();
此時的node的引用計數(shù)是1
調用了Node的create工廠方法進行創(chuàng)建,查看create方法的實現(xiàn)
Node * Node::create()
{
Node * ret = new (std::nothrow) Node();
if (ret && ret->init())
{
ret->autorelease();
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}
node調用了autorelease方法赵讯,此方法為Ref類的方法
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
其實是把當前的node放到了自動釋放池中進行管理盈咳。PoolManager調用getInstance的方法獲取實例,查看里面的實現(xiàn)
PoolManager* PoolManager::getInstance()
{
if (s_singleInstance == nullptr)
{
s_singleInstance = new (std::nothrow) PoolManager();
// Add the first auto release pool
new AutoreleasePool("cocos2d autorelease pool");
}
return s_singleInstance;
}
可以看到自動釋放池也是在此時創(chuàng)建的边翼。以前引擎版本的自動釋放池是在引擎初始化的時候創(chuàng)建的鱼响。
PoolManager類的getCurrentPool方法返回的是當前的自動釋放池
AutoreleasePool* PoolManager::getCurrentPool() const
{
return _releasePoolStack.back();
}
_releasePoolStack是一個vector,但是現(xiàn)在當成棧來用组底。
2:在mainLoop中進行處理
mainLoop是上篇文章說的主循環(huán)的操作丈积,每一幀都會調用
void Director::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (_restartDirectorInNextLoop)
{
_restartDirectorInNextLoop = false;
restartDirector();
}
else if (! _invalid)
{
drawScene();
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
此處調用了PoolManager::getInstance()->getCurrentPool()->clear()查看clear中的代碼
std::vector<Ref*> releasings;
releasings.swap(_managedObjectArray);
for (const auto &obj : releasings)
{
obj->release();
}
_managedObjectArray為自動釋放池中的對象容器;releasings是個空的容器债鸡。
經過releasings.swap(_managedObjectArray)這個操作之后_managedObjectArray就是空的了江滨,就是說加入了自動釋放池中的對象只會被管理一次,后面不會再被管理了厌均。
對releasings中的對象(其實就是剛才內存釋放池中的對象)進行遍歷唬滑,進行release操作,此為Ref類中的方法
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
--_referenceCount;
if (_referenceCount == 0)
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
auto poolManager = PoolManager::getInstance();
if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
{
// Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.
// This happens when 'autorelease/release' were not used in pairs with 'new/retain'.
//
// Wrong usage (1):
//
// auto obj = Node::create(); // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool.
// obj->autorelease(); // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first.
//
// Wrong usage (2):
//
// auto obj = Node::create();
// obj->release(); // Wrong: obj is an autorelease Ref, it will be released when clearing current pool.
//
// Correct usage (1):
//
// auto obj = Node::create();
// |- new Node(); // `new` is the pair of the `autorelease` of next line
// |- autorelease(); // The pair of `new Node`.
//
// obj->retain();
// obj->autorelease(); // This `autorelease` is the pair of `retain` of previous line.
//
// Correct usage (2):
//
// auto obj = Node::create();
// obj->retain();
// obj->release(); // This `release` is the pair of `retain` of previous line.
CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
}
#endif
#if CC_ENABLE_SCRIPT_BINDING
ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
{
pEngine->removeObjectProxy(this);
}
#endif // CC_ENABLE_SCRIPT_BINDING
#if CC_REF_LEAK_DETECTION
untrackRef(this);
#endif
delete this;
}
}
大家可以看到棺弊,把引用計數(shù)減1晶密,如果此時引用計數(shù)為0,則釋放此Ref對象模她。
剛才創(chuàng)建的node稻艰,此時就會被釋放掉了。如果使用了成員變量或者全局變量指向了剛才的node侈净,那么剛才的node就是個野指針了尊勿。
3:添加到場景中的Node的引用計數(shù)情況
創(chuàng)建了的node添加到場景(其實也是個Node)中,此時引用計數(shù)就會+1用狱,變成2
Node* node = Node::create(); // 此時node的引用計數(shù)為1
Layer* layer = Layer::create();
layer->addChild(node); // 此時node的引用計數(shù)為2
為什么addChild了以后引用計數(shù)就變成2了呢运怖,來看看代碼
void Node::addChild(Node* child, int localZOrder, const std::string &name)
調用addChildHelper(child, localZOrder, INVALID_TAG, name, false)
調用Node::insertChild(Node* child, int z)
調用_children.pushBack(child)
調用_data.push_back( object );
object->retain();
調用++_referenceCount;
原來Node(Layer繼承自Node)內部有個CCVector,每次添加Ref到CCVector都會retain一下夏伊,會增加一次引用計數(shù)。
相應的在removechild的時候會調用release吻氧,此時引用計數(shù)減一溺忧,如果此時引用計數(shù)為0了,則立刻釋放盯孙,就不用在自動釋放池中釋放了鲁森。所以自動釋放池只要管理一次就可以了。
4:如何讓Ref不被釋放
其實很簡單振惰,retain一下就可以了歌溉。
調用addChild方法會增加一次引用計數(shù)。有些Ref對象是不用添加到場景中的,比如CCArray痛垛,只是用來對場景中的元素進行管理草慧,那么在創(chuàng)建一個CCArray對象以后,主動retain一下匙头,就不會被自動釋放池清除掉漫谷。但是在場景結束時,要relase掉蹂析,不然就會出現(xiàn)內存泄漏舔示。
自動釋放池的嵌套
如果創(chuàng)建了大量的Ref變量,那么在mainLoop的時候就有可能出現(xiàn)釋放時間過長电抚,導致下一次繪圖間隔太久惕稻,導致界面上卡頓。如果有此種情況蝙叛,可以考慮自己創(chuàng)建自動釋放池來存放Ref變量俺祠,用完就釋放掉。
此時PoolManager的管理功能就派上用場了甥温。下面來看看這個過程锻煌。
使用場景:創(chuàng)建了大量的String(cocos中封裝的字符類,非標準庫中的類)對象對字符串進行操作姻蚓,由于字符對象是不用加載到場景中的宋梧,所以在使用完成以后在本幀結束后就會被釋放掉。
AutoreleasePool* pool = new AutoreleasePool("My release pool"); // 創(chuàng)建我們自己的auto release pool
String* string1 = String::createWithFormate("nihao%d", 1);
...
...
String* string1000 = String::createWithFormate("nihao%d", 1000);
delete pool; // 釋放我們自己創(chuàng)建auto release pool
分析一下上述流程:
1:創(chuàng)建了一個我們自己的自動釋放池名字叫:My release pool狰挡。查看AutoreleasePool的構造函數(shù)
AutoreleasePool::AutoreleasePool(const std::string &name)
: _name(name)
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{
_managedObjectArray.reserve(150);
PoolManager::getInstance()->push(this);
}
PoolManager會將我們剛剛創(chuàng)建的自動釋放池加入到內部的棧中捂龄。此時通過getCurrentPool獲取的就是剛才創(chuàng)建的自動釋放池。
2:創(chuàng)建字符串變量指針加叁,createWithFormate里面調用了autorelease函數(shù)倦沧,此時這些變量的指針都會添加到我們剛才創(chuàng)建的自動釋放池中。
3:刪除自動釋放池它匕。查看AutoreleasePool的析構函數(shù)展融。
AutoreleasePool::~AutoreleasePool()
{
CCLOGINFO("deallocing AutoreleasePool: %p", this);
clear();
PoolManager::getInstance()->pop();
}
此時會先調用clear函數(shù)釋放池中的指針。然后PoolManager會將當前的自動釋放池從內部的棧中移除豫柬。此時PoolManager調用getCurrentPool返回的就是默認的自動釋放池告希。
總結:
1:自動釋放池對每個Ref對象指針管理一次,后面就在恰當?shù)臅r機進行釋放烧给。
2:CCVetctor在添加Ref對象指針的時候會retain一次燕偶,在移除的時候會release一次。
3:自動釋放池可以嵌套使用础嫡。