cocos2dx - 节点管理

时间:2023-03-10 02:26:10
cocos2dx - 节点管理

接上一节内容:cocos2dx - v2.3.3编辑器骨骼动画

本节主要Cocos2dx中节点的管理及应用

一般用法

  用过Cocos2dx应该都有用过addChild,removeChild方法。或者retain,release方法。

addChild,removeChild方法都是继承Node类,retain,release方法则继承自Ref类。

查看cocos2dx库代码可以看到,Ref中retain,release主要维持了一个_referenceCount计数变量

通过retain方法可以使_referenceCount计数加1,同时release可以使_referenceCount计数减1,并且在_referenceCount计数为0时,释放对象。

void Ref::retain()
{
CCASSERT(_referenceCount > , "reference count should be greater than 0");
++_referenceCount;
} void Ref::release()
{
CCASSERT(_referenceCount > , "reference count should be greater than 0");
--_referenceCount; if (_referenceCount == )
{
delete this;
}
}

查看Node的实现也可以了解Node中维护了一个CCVector对象来存储子节点,在erase子节点时,对子节点调用release方法,同时在pushBack新节点时,对子节点调用retain方法。

下面是CCVector中pushBack及erase的一些细节:

    // Adds objects

    /** 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();
}
/** @brief Removes from the vector with an iterator.
* @param position Iterator pointing to a single element to be removed from the Vector.
* @return An iterator pointing to the new location of the element that followed the last element erased by the function call.
* This is the container end if the operation erased the last element in the sequence.
*/
iterator erase(iterator position)
{
CCASSERT(position >= _data.begin() && position < _data.end(), "Invalid position!");
(*position)->release();
return _data.erase(position);
}

这样就说明了Node节点实际还是在对Ref继承的_referenceCount进行计数处理。

若Node没有添加到父节点中,那么_referenceCount什么时候变成0,又什么时候释放节点的?

首先在Ref的构造函数中,可以看到_referenceCount初始化为1

Ref::Ref()
: _referenceCount() // when the Ref is created, the reference count of it is 1
{
#if CC_ENABLE_SCRIPT_BINDING
static unsigned int uObjectCount = ;
_luaID = ;
_ID = ++uObjectCount;
_scriptObject = nullptr;
#endif #if CC_REF_LEAK_DETECTION
trackRef(this);
#endif
}

同时,在Node的静态创建方法Node::create中,宏定义  CREATE_FUNC(Node)  实现如下,

/** @def CREATE_FUNC(__TYPE__)
* Define a create function for a specific type, such as Layer.
*
* @param __TYPE__ class type to add create(), such as Layer.
*/
#define CREATE_FUNC(__TYPE__) \
static __TYPE__* create() \
{ \
__TYPE__ *pRet = new(std::nothrow) __TYPE__(); \
if (pRet && pRet->init()) \
{ \
pRet->autorelease(); \
return pRet; \
} \
else \
{ \
delete pRet; \
pRet = nullptr; \
return nullptr; \
} \
}

可以看到,在对象创建失败会直接delete,并返回nullptr。创建成功则调用了autorelease方法,如下:

Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}

说明在正常创建Node节点时,会将其添加到coco2dx的一个内存池进行管理。同时在主循环中看到,下一帧处理对当前内存池对象进行了一次清理,并调用了release方法。

void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (_restartDirectorInNextLoop)
{
_restartDirectorInNextLoop = false;
restartDirector();
}
else if (! _invalid)
{
drawScene(); // release the objects
PoolManager::getInstance()->getCurrentPool()->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
}

因此,如果一个对象在创建后,没有及时进行retain或者添加到父节点中,在当前帧结束时被调用release方法导致 _referenceCount变成0,从而被释放。

几个示例:

Scene* pScene = Director::getInstance()->getRunningScene();
if (pScene)
{
Node* pNode1 =Node::create(); // pNode1创建后没有处理,在当前帧结束前被释放 Node* pNode2 =Node::create(); // pNode2创建后添加到场景中,不会被释放
pScene->addChild(pNode2); Node* pNode3 =pScene->getChildByTag();
if(pNode3)
{
pScene->removeChild(pNode3); // pNode3被立即释放
pNode2->addChild(pNode3); // 错误!! pNode3已被释放
} Node* pNode4 =Node::create();
pNode2->addChild(pNode4); // pNode4 随 pNode2释放 Node* pNode5 =Node::create();
pScene->addChild(pNode5);
pNode5->retain();
pScene->removeChild(pNode5);
pNode2->addChild(pNode5);
pNode5->release(); // pNode5 随 pNode2释放 }