cocos2dx的内存管理,我有空追踪一下源代码 理解一下。
我们都知道 cocos2dx是靠引用计数实现的,顺便加入了对象池的管理。
涉及到一下几个类,autorealsePool,PoolManager,Ref,displaylinkDirector.
函数:
mainLoop,retain,release,autoRealse.
,
int Application::run()
{
PVRFrameEnableControlWindow(false);
// Main message loop:LARGE_INTEGER nLast;LARGE_INTEGER nNow;queryPerformanceCounter(&nLast);initGLContextAttrs();// Initialize instance and cocos2d.if (!applicationDIDFinishLaunching()){ return 0;}auto director = Director::getInstance();auto glvIEw = director->getopenGLVIEw();// Retain glvIEw to avoID glvIEw being released in the while loopglvIEw->retain();//如果窗口未关闭 就是一个while死循环while(!glvIEw->windowshouldClose()){ queryPerformanceCounter(&nNow); if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart) { nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart); director->mainLoop(); glvIEw->pollEvents(); } else { Sleep(1); }}// Director should still do a cleanup if the window was closed manually.if (glvIEw->isOpenglready()){ director->end(); director->mainLoop(); director = nullptr;}glvIEw->release();return true;
}
,
voID displaylinkDirector::mainLoop(){ if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (_restartDirectorInNextLoop) { _restartDirectorInNextLoop = false; restartDirector(); } else if (! _invalID) { drawScene(); // release the objects auto count = PoolManager::getInstance()->getPoolCount();//这个函数我自己加的,方便打印 cclOG("$$$$$$$$$$=========== %d",count); cclOG("&&&&&&&&&&=========== %s",PoolManager::getInstance()->getCurrentPool()->getname());//这个函数我自己加的,方便打印 auto pool = PoolManager::getInstance()->getCurrentPool(); cclOG("前面pool count is %d",pool->getCount()); PoolManager::getInstance()->getCurrentPool()->clear(); cclOG("后面pool count is %d",pool->getCount()); }}
其他的先不管 ,就看PoolManager::getInstance()->getCurrentPool()->clear();这行代码。
PoolManager这个类,从字面上的意思它是个管理类,所以它被写成单例模式。单例的代码:
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;}
以上代码是个典型的单例模式写法,但是有行代码
new autoreleasePool(“cocos2d autorelease pool”);我从没看过生成一个对象 却不用其他的引用或者指针指向它,写的好奇怪。
查看他的构造函数:
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);}
原来autoreleasePool类生成对象的时候,在构造函数中调用
PoolManager::getInstance()->push(this)。
voID PoolManager::push(autoreleasePool *pool){ _releasePoolStack.push_back(pool);}
看懂了吧,原来push函数原来在这里调用了,但是这种写法很奇怪,可能是我c++功力太浅了,要是我直接在单例getInstance函数中push了。
PoolManager类成员变量:
std::vector 《autoreleasePool*》 _releasePoolStack
其实这个_releasePoolStack断点的时候,只有在生成单例模式的push了一个autoreleasePool对象,_releasePoolStack的size() = 1。
所以在mainLoop中:
auto count = PoolManager::getInstance()->getPoolCount();cclOG("$$$$$$$$$$=========== %d",count);定义如下int PoolManager::getPoolCount(){ return _releasePoolStack.size();}
打印始终都是size等于1
autoreleasePool* PoolManager::getCurrentPool() const{ return _releasePoolStack.back();}
这个函数返回的是_releasePoolStack的末尾引用
而_releasePoolStack的数据size其实只有一条,既然是一条,那干嘛用vector呢?这点没弄明白 难道是因为喜欢用vector存贮数据?
接下来看autoreleasePool::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}
_managedobjectArray数据被清空,这个函数中有个重要的知识点 那就是vector的swap的用法,swap可以清空vector申请的内存,而clear却不一定。详情可参照:
简单的程序诠释C++ STL算法系列之十五:swap
追踪release代码:
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)) { CCASSERT(false,"The reference shouldn't be 0 because it is still in autorelease pool."); } #endif #if CC_REF_LEAK_DETECTION untrackRef(this); #endif delete this; } }
release函数 就是将计数自减一,将0的Ref析构掉。
每个集成了Ref的对象 都有属性_referenceCount,那它在哪里自增呢?
举个例子,去看看Sprite的create函数
Sprite* Sprite::create(const std::string& filename){ Sprite *sprite = new (std::nothrow) Sprite(); if (sprite && sprite->initWithfile(filename)) { sprite->autorelease(); return sprite; } CC_SAFE_DELETE(sprite); return nullptr;}
autorelease函数的定义
Ref* Ref::autorelease(){ PoolManager::getInstance()->getCurrentPool()->addobject(this); return this;}
再追踪addobject函数
voID autoreleasePool::addobject(Ref* object){ _managedobjectArray.push_back(object);}
奇怪 不是说好 一个精灵创建的时候 引用计数就加1吗?但是我们找了半天没发现,原来是Ref构造的时候就默认设置成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}
我们知道 cocos2dx 中node sprite都是继承Ref,C++中对象生成的时候,先调用父类的构造函数 ,再调用自己的构造函数。
4.引用计数 自增和自减 在哪些地方出现? addChild()函数Node下面好几个addChild函数,都是函数重载,最终都用到这个函数:
addChildHelper()
voID Node::addChildHelper(Node* child,int localZOrder,int tag,const std::string &name,bool setTag){ if (_children.empty()) { this->childrenAlloc(); } this->insertChild(child,localZOrder); if (setTag) child->setTag(tag); else child->setname(name); child->setParent(this); child->setorderOfArrival(s_globalOrderOfArrival++);#if CC_USE_PHYSICS // Recursive add children with which have physics body. auto scene = this->getScene(); if (scene && scene->getPhysicsWorld()) { scene->addChildtophysicsWorld(child); }#endif if( _running ) { child->onEnter(); // prevent onEnterTransitionDIDFinish to be called twice when a node is added in onEnter if (_isTransitionFinished) { child->onEnterTransitionDIDFinish(); } } if (_cascadecolorEnabled) { updateCascadecolor(); } if (_cascadeOpacityEnabled) { updateCascadeOpacity(); }}
找到insertChild函数:
voID Node::insertChild(Node* child,int z){ _transformUpdated = true; _reorderChildDirty = true; _children.pushBack(child); child->_localZOrder = z;}
接着找到pushBack函数,开始以为就是vector中的函数呢,其实不然:
voID pushBack(T object) { CCASSERT(object != nullptr,"The object should not be nullptr"); _data.push_back( object ); object->retain(); }
终于找到retain函数了啊!
setParent函数:voID Node::setParent(Node * parent){ _parent = parent; _transformUpdated = _transformDirty = _inverseDirty = true;}
发现这个函数根本没有使引用计数增加,而我在项目中使用setParent的时候 根本一个节点没有显示出来,所以这个函数只是一个指向关系,并未将孩子真正加入到父节点中,所以我基本不使用它,我都是使用addChild函数。
所以当你生成一个Sprite并把它加入到layer之后,他的引用计数是2。
removeFromParent函数:voID Node::removeFromParent(){ this->removeFromParentAndCleanup(true);}voID Node::removeFromParentAndCleanup(bool cleanup){ if (_parent != nullptr) { _parent->removeChild(this,cleanup); } }
我们可以看到这两个函数几乎等同,接着removeChild:
voID Node::removeChild(Node* child,bool cleanup /* = true */){ // explicit nil handling if (_children.empty()) { return; } ssize_t index = _children.getIndex(child); if( index != CC_INVALID_INDEX ) this->detachChild( child,index,cleanup );}
接着detachChild:
voID Node::detachChild(Node *child,ssize_t childindex,bool doCleanup){ // important: // -1st do onExit // -2nd cleanup if (_running) { child->onExitTransitionDIDStart(); child->onExit(); }#if CC_USE_PHYSICS child->removeFromPhysicsWorld();#endif // If you don't do cleanup,the child's actions will not get removed and the // its scheduledSelectors_ dict will not get released! if (doCleanup) { child->cleanup(); } // set parent nil at the end child->setParent(nullptr); _children.erase(childindex);}
接着cleanup函数:
voID Node::cleanup(){ // actions this->stopAllActions(); this->unscheduleAllCallbacks();#if CC_ENABLE_SCRIPT_BINDING if ( _scriptType != kScriptTypeNone) { int action = kNodeOnCleanup; BasicScriptData data(this,(voID*)&action); ScriptEvent scriptEvent(kNodeEvent,(voID*)&data); ScriptEngineManager::getInstance()->getScriptEngine()->sendEvent(&scriptEvent); }#endif // #if CC_ENABLE_SCRIPT_BINDING // timers for( const auto &child: _children) child->cleanup();}
可以看到 递归调用了cleanup函数 子节点都是处理一些事情,detachChild函数最后将子节点的父节点设置为空,最后看erase函数:
iterator erase(ssize_t index) { CCASSERT(!_data.empty() && index >=0 && index < size(),"InvalID index!"); auto it = std::next( begin(),index ); (*it)->release(); return _data.erase(it); }
终于找到了release函数了。
有时候我容易将 std::vector和cocos2dx封装的Vector搞混淆,真是眼睛不好啊!现在内存管理的基本思路搞清楚了。
总结以上是内存溢出为你收集整理的cocos2dx 3.x内存管理源代码追踪全部内容,希望文章能够帮你解决cocos2dx 3.x内存管理源代码追踪所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)