在cocos2dx-3.17.2
中的自動內存管理機制是藉助引用計數來實現的。內存管理的實現基於Ref這個類,基本的原理就是其內部存在一個引用計數_referenceCount
,當這個引用計數爲0的時候,就會被釋放。引用計數通過retain
,release
來操作。
/**
* Ref is used for reference count management. If a class inherits from Ref,
* then it is easy to be shared in different places.
* @js NA
*/
class CC_DLL Ref
{
/* 省略 */
void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
++_referenceCount;
}
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
--_referenceCount;
/* 省略 */
}
};
接下來從節點的創建到回收來說明Cocos的內存管理機制:
創建一個Node:
Node::create()
Node * Node::create()
{
Node * ret = new (std::nothrow) Node();
if (ret && ret->init())
{
ret->autorelease();
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}
Ref::aurorelease(): //將節點加入到自動釋放池:
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
AutoreleasePool::addObject(Ref* object) :
void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
}
Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
/* 省略 */
}
創建節點後,節點的_referenceCount = 1。
可知,每一個創建的節點在不進行其他操作的情況下默認的_referenceCount == 1,_referenceCount 值的初始化在Ref類中。
添加節點到場景中:
Node::addChild(Node *child, int localZOrder, int tag):
/* "add" logic MUST only be on this method
* If a class want's to extend the 'addChild' behavior it only needs
* to override this method
*/
void Node::addChild(Node *child, int localZOrder, int tag)
{
/* 省略 */
addChildHelper(child, localZOrder, tag, "", true);
}
Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag):
void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)
{
/* 省略 */
this->insertChild(child, localZOrder);
/* 省略 */
}
void Node::insertChild(Node* child, int z):
// helper used by reorderChild & add
void Node::insertChild(Node* child, int z)
{
/* 省略 */
_children.pushBack(child);
/* 省略 */
}
Vector::pushBack(T object) : //節點創建時,就被添加了引用。這裏的vector 是cocos重新定義的容器,不是std::vector
/** Adds a new element at the end of the Vector. */
void pushBack(T object)
{
CCASSERT(object != nullptr, "The object should not be nullptr");
_data.push_back( object );
object->retain();
}
講節點添加到場景中後,節點的_referenceCount = 2。
可知,再將節點添加到場景中,也就是作爲另一個節點的子節點時,_referenceCount 會+1。
自動回收內存機制:
void Director::mainLoop(): //負責調用定時器,繪圖,發送全局通知,並處理內存回收池。每一幀進行一次調用。
void Director::mainLoop()
{
/* 省略 */
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
void AutoreleasePool::clear(): //清空自動釋放池中的對象,並且釋放所有_referenceCount = 0 的節點內存
void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = true;
#endif
std::vector<Ref*> releasings;
releasings.swap(_managedObjectArray);
for (const auto &obj : releasings)
{
obj->release();
}
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = false;
#endif
}
void Ref::release(): //如果節點_referenceCount = 0 ,釋放節點內存
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
--_referenceCount;
if (_referenceCount == 0)
{
/* 省略 */
#if CC_REF_LEAK_DETECTION
untrackRef(this);
#endif
delete this;
}
}
可以看到,void AutoreleasePool::clear():裏面對所有在自動釋放池(_managedObjectArray)
中的對象都進行了一次release
操作,並把_managedObjectArray清空。
可知,當我們創建Node的時候
_referenceCount == 1,然後添加到場景之後 _referenceCount == 2,當場景繪製一幀以後系統會自動調用void AutoreleasePool::clear()函數,會遍歷_managedObjectArray中所有的對象並執行其的release()函數,最後清空_managedObjectArray,此時節點的
_referenceCount == 1。
移除節點所在的場景:
void Director::replaceScene(Scene *scene):
void Director::replaceScene(Scene *scene)
{
/* 省略 */
if (_nextScene)
{
if (_nextScene->isRunning())
{
_nextScene->onExit();
}
_nextScene->cleanup();
_nextScene = nullptr;
}
/* 省略 */
_scenesStack.replace(index, scene);
}
void Node::cleanup(): //停止場景中所有節點的Action和schedule
void Node::cleanup()
{
/* 省略 */
// actions
this->stopAllActions();
// timers
this->unscheduleAllCallbacks();
for( const auto &child: _children)
child->cleanup();
}
Vector::replace(): //釋放當前場景的內存,並保存要運行的場景
/** Replace value at index with given object. */
void replace(ssize_t index, T object)
{
CCASSERT(index >= 0 && index < size(), "Invalid index!");
CCASSERT(object != nullptr, "The object should not be nullptr");
_data[index]->release();
_data[index] = object;
object->retain();
}
可知,當我們移除場景時,首先會停止場景中所有節點的Action和schedule,然後釋放當前場景的內存,並保存要運行的場景。
自動回收池作用在每幀結束的時候,在一幀開始之前,系統建立了一個內存回收池,在這一幀的過程中,當我們調用autorelease方法以後,我們的對象就會放到這個內存回收池中,當一幀結束的時候這個內存回收池就會釋放掉,這個時候在內存回收池中的對象就會被release,也就是說當前內存回收池中的所有對象的referenceCount 都會 -1,如果這個時候referenceCount爲0,就會釋放節點的內存。如果節點引用計數不爲0的話對象是不會被刪除的。
下一幀開始的時候系統又會創建一個內存回收池,這個時候在上一次添加的對象這個時候是不會重新添加到這個內存回收池中的,在這個內存回收池中的對象是你在這一幀中調用了autorelease函數的節點。