Cocos內存管理解析 CCRef/retain/release/autorelease
Cocos內存管理源碼(autorelease解析)
背景
這段時間在做項目的時候,需求需要往spine動作的掛點上綁定按鈕節點,由于按鈕在編輯器中是加在已有節點上的,所以在往spine上添加掛點時,需要先移除按鈕,然后再綁定的掛點上。
local spineAnim = sp.SkeltonAnimation:create(skeletonFile, atlasFile, 1.0, true)
local btnGame = roleNode:getChildByName("btnGame")
btnGame:removeFromParent()
spineAnim:addSlotBindInfo("qianwangduiju", btnGame, Defind.slotBindType.slotBindType_all)
如果直接這樣寫,會在某種情況導致btnGame按鈕節點丟失,無法正常掛載再spine動畫節點上,后續優化了此方案
local spineAnim = sp.SkeltonAnimation:create(skeletonFile, atlasFile, 1.0, true)
local btnGame = roleNode:getChildByName("btnGame")
btnGame:retain()
btnGame:removeFromParent()
btnGame:autorelease()
spineAnim:addSlotBindInfo("qianwangduiju", btnGame, Defind.slotBindType.slotBindType_all)
在移除按鈕之前,先retain一下,這樣引用計數加1,就不會導致內存被回收,再調用autorelease,此時并不會release對象,這時會將此節點加入_managedObjectArry對象池中,在Director的mainLoop中會調用PoolManager::getInstance()->getCurrentPool()->clear();
具體詳情解析,請接著看
在cocos2dx-3.8中的自動內存管理是用引用計數來實現的,對于老版本的coocs引用計數使用的是CCObejct,但這個類后續被棄用了,使用CCRef代替,cocos中幾乎所有的類都是繼承于CCRef
CCRef基本原理就是其內部存在一個引用計數_referenceCount,當這個計數為0時,就會被釋放。引用計數通過retain,release操作。
Ref從創建到銷毀的過程
舉個栗子,向屏幕中添加一個Button來測試Ref的創建和銷毀,首先創建一個Button
auto button = Button::create();
button->setName("myButton");
addChild(button);
以上代碼就是向屏幕中添加一個button,讓我們看看create做了什么
Button* Button::create()
{
Button* widget = new (std::nothrow) Button();
if (widget && widget->init())
{
widget->autorelease();
return widget;
}
CC_SAFE_DELETE(widget);
return nullptr;
}
create函數是一個工廠方法,cocos中很多類都實現了這個方法,其中可以看到ret->autorelease();,這個函數就是把當前對象加入到自動釋放池內,對于自動釋放池下面會詳細講解。(注:Ref初始化的時候引用計數為1不是0)
接下來看下addChild()接口,此處截取了一部分
void Node::addChild(Node *child){
CCASSERT( child != nullptr, "Argument must be non-nil");
this->addChild(child, child->_localZOrder, child->_name);
}
void Node::addChild(Node* child, int localZOrder, const std::string &name){
...
addChildHelper(child, localZOrder, INVALID_TAG, name, false);
}
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){
_transformUpdated = true;
_reorderChildDirty = true;
_children.pushBack(child);
child->_localZOrder = z;
}
最終跳轉到insertChild中,通過 _children.pushBack把 button加入到_children中去,到底引用計數在哪里+1操作?答案在pushBack操作中,_children是cocos為Ref量身定制的向量Vector<T>,這個向量只能給繼承Ref的類使用
void pushBack(T object){
...
_data.push_back( object );
object->retain();
}
代碼中可以看到object->retain(),對添加進來的對象引用+1操作,那么什么時候-1呢?
當我們移除場景的時候,應該釋放場景中的button的。Node被移除時會調用當前的Node的父親的removeChild函數,此函數最后會調用Node的cleanup函數,cleanup函數時遞歸函數,會遍歷所有子節點。當cleanup完之后會從父節點的_children這個向量中刪除,此時就會調用release函數
//當某個兒子節點cleanup完之后會調用_children.earse(childIndex)
iterator erase(ssize_t index){
...
auto it = std::next( begin(), index );
(*it)->release();
return _data.erase(it);
}
release函數就是當前實例的引用計數-1,如果-1后為0那么釋放內存
void Ref::release(){
...
--_referenceCount;
if (_referenceCount == 0){
...
delete this;
}
}
自動釋放池
Ref中的autorelease函數,咋一看感覺內存不需要我來管了,他會自動釋放。然而這個自動和我腦子里面的自動向差一個孫猴子的跟頭,畢竟c++不是java,先看看autorelease的源碼
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
代碼很短,autorelease并沒有release,而是把對象加入到了對象池中。那么這個對象池是什么時候去release里面的對象呢?接下來就要看Director的mainLoop函數了,這個函數在Director中實現。
void Director::mainLoop()
{
if(_purgeDirectorInNextLoop)
{
...
}
else if(_restartDirectorInNextLoop)
{
...
}
else if(!_invalid)
{
drawScene();
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
mainLoop是每一幀調用的函數,我們發現cocos在每一幀結束繪制drawScene之后都會調用PoolManager::getInstance()->getCurrentPool()->clear();的操作,接下來我們看看clear的實現細節。
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
}
我們發現,在clear里面對所有在_managedObjectArray中的所有對象都進行一次release操作,并把它從_managedObjectArray中刪掉。_managedObjectArray是什么,查看前一段代碼中addObject的實現細節就知道,autorelease就是把當前對象加入到_managedObjectArray中
也就是說,我們創建的Button的時候引用計數為1,然后調用autorelease添加到_managedObjectArray中,之后又被addChild到屏幕中,此時引用計數為2。當一幀繪制結束的時候會系統會調用釋放池的clear函數,此函數會遍歷所有在自動釋放池內的對象并release,最后從對象池中刪除之(所以第二幀結束后不會被再次調用release了),此時引用計數為1。當我們把當前場景移除的時候會調用release把引用計數減少至0,并從內存中釋放。


浙公網安備 33010602011771號