接上一节内容: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 > 0, "reference count should be greater than 0"); ++_referenceCount; } void Ref::release() { CCASSERT(_referenceCount > 0, "reference count should be greater than 0"); --_referenceCount; if (_referenceCount == 0) { 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(1) // when the Ref is created, the reference count of it is 1 { #if CC_ENABLE_SCRIPT_BINDING static unsigned int uObjectCount = 0; _luaID = 0; _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(3); 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释放 }