赞
踩
Node类是绝大部分类的父类,如Scene、Layer,Sprite,Button等,要理解cocos的内存管理,首先的是从Node类源码开始入手,抽丝剥茧,一步一步去挖掘
- Node * Node::create()
- {
- Node * ret = new (std::nothrow) Node();
- if (ret && ret->init())
- {
- ret->autorelease();
- }
- else
- {
- CC_SAFE_DELETE(ret);
- }
- return ret;
- }
- void Node::addChild(Node* child, int localZOrder, const std::string &name)
- {
- CCASSERT(child != nullptr, "Argument must be non-nil");
- CCASSERT(child->_parent == nullptr, "child already added. It can't be added again");
- addChildHelper(child, localZOrder, INVALID_TAG, name, false);
- }
-
- void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)
- {
- if (_children.empty())
- {
- this->childrenAlloc();
- }
- this->insertChild(child, localZOrder);
- ......
- }
- void Node::childrenAlloc()
- {
- _children.reserve(4);
- }
- // helper used by reorderChild & add
- void Node::insertChild(Node* child, int z)
- {
- _transformUpdated = true;
- _reorderChildDirty = true;
- _children.pushBack(child);
- child->_localZOrder = z;
- }
-
- void Node::removeFromParent()
- {
- this->removeFromParentAndCleanup(true);
- }
-
- void Node::removeFromParentAndCleanup(bool cleanup)
- {
- if (_parent != nullptr)
- {
- _parent->removeChild(this,cleanup);
- }
- }
-
- 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 );
- }
-
- 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 );
- }
-
- void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
- {
- // IMPORTANT:
- // -1st do onExit
- // -2nd cleanup
- if (_running)
- {
- child->onExitTransitionDidStart();
- child->onExit();
- }
-
- // 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);
- }

1、首先看create函数,这就函数是创建节点,这里需要注意是就是autorelease()这个方法,这个方法是干什么的呢?疑问1我们继续往下找,这个方法是Node父类的,顺着这条线,我们找到了Ref这个类,Ref是啥呢?这里先保留这个疑问2,等下在讲,现在继续看addChild()函数
2、再看addChild()->addChildHelper()->insertChild()->_children.pushBack(child)这个函数就是把节点pushBack到_children这个容器中去,然后设置name、tag,localZoOder,_children是什么呢?在Node中找,最后找到_children是一个Vector,装子节点的,每个Node上都有一个_children。这里可以看到pushBack这里调用了retain(),函数retain是啥呢?疑问3
3、最后看removeFromParent()->removeFromParentAndCleanup()->removeChild()->detachChild()->_children.erase(childIndex),这个函数就是把节点从_children中erase(),这里可以看到erase这里调用了release(),函数release()是啥呢?疑问4
- Vector<Node*> _children; ///< array of children nodes
-
- void pushBack(T object)
- {
- CCASSERT(object != nullptr, "The object should not be nullptr");
- _data.push_back( object );
- object->retain();
- }
-
- 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);
- }

Ref到底是啥呢?还是从源码入手,我们找到Ref的源码,主要包括如下的函数:
- class CC_DLL Ref
- {
- public:
- void retain(); // ++_referenceCount
- void release(); // --_referenceCount
- Ref* autorelease(); // 添加到AutoReleasePool
- unsigned int getReferenceCount() const; // 获取引用次数
-
- protected:
- unsigned int _referenceCount; // 记录引用次数
- };
-
- NS_CC_END
-
- #endif // __BASE_CCREF_H__
- Ref::Ref()
- : _referenceCount(1) // 当创建Ref时,它的引用计数为1
- {
- #if CC_ENABLE_SCRIPT_BINDING
- static unsigned int uObjectCount = 0;
- _luaID = 0;
- _ID = ++uObjectCount;
- #endif
-
- #if CC_REF_LEAK_DETECTION
- trackRef(this);
- #endif
- }
-
- void Ref::retain()
- {
- CCASSERT(_referenceCount > 0, "reference count should greater than 0");
- ++_referenceCount;
- }
-
- void Ref::release()
- {
- CCASSERT(_referenceCount > 0, "reference count should 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;
- }
- }
- Ref* Ref::autorelease()
- {
- PoolManager::getInstance()->getCurrentPool()->addObject(this);
- return this;
- }

如何理解这个段代码呢?由上面代码可知道,cocos2d采用了引用计数的方法来进行来对对象进行 内存管理,(疑问2回答),Ref是cocos的内存管理类,cocos2d几乎所有的对象节点继承于Ref
1、首先看构造函数,当创建对象时,它的引用计数赋值为1(重点)
2、(疑问3回答)然后看retain函数,这个接口作用就是_referenceCount+1
3、(疑问4回答)接着看release函数,这个接口作用就是_referenceCount-1,当_referenceCount==0的时候,对象会被回收释放(重点)
4、再看autorelease()函数,这个函数有什么用呢?留疑1问接着往下看,这个函数里面出现了PoolManager这个单例,回到Ref.h,我们找到了AutoreleasePool这个类
friend class AutoreleasePool;
- void AutoreleasePool::addObject(Ref* object)
- {
- _managedObjectArray.push_back(object);
- }
-
- void AutoreleasePool::clear()
- {
- #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
- _isClearing = true;
- #endif
- std::vector<Ref*> releasings;
- //将_managedObjectArray的数据和releasings交换,
- //以达到清理上一帧的_managedObjectArray这个vector,
- //来确保已经被release过的节点已经被踢出releasePool,具体请参考
- //c++ vector.swap原理
- releasings.swap(_managedObjectArray);
- // 遍历所有对象,进行引用计数-1,为0的销毁对象
- for (const auto &obj : releasings)
- {
- obj->release();
- }
- #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
- _isClearing = false;
- #endif
- }
-
- AutoreleasePool* PoolManager::getCurrentPool() const
- {
- return _releasePoolStack.back();
- }

AutoReleasePool,解释过来就是自动释放池,是来存放Node:create() 的一个容器。这里终于可以解释疑问1了,autorelease()就是把对象加入到了自动释放对象池中,这里只是添加进去了,哪里调用了AutoreleasePool::clear()函数呢?继续往下找,在CCDirector单例的manLoop()中找到了这个函数的调用:
- void DisplayLinkDirector::mainLoop()
- {
- if (_purgeDirectorInNextLoop)
- {
- _purgeDirectorInNextLoop = false;
- purgeDirector();
- }
- else if (! _invalid)
- {
- drawScene();
-
- // release the objects
- PoolManager::getInstance()->getCurrentPool()->clear();
- }
- }
coocs2d每一帧都会对当前AutoreleasePool进行clear(),把引用计数为0的对象清理。这里有几点需要解释一下
1、如果一个对象在创建后,在当前帧没有调用addChild(), 对象就会被销毁掉,所以对象一定要在当前帧结束前调用
2、对象在addChild()后_referenceCount由1变成2了(在Node和Ref有讲到过),然后通过AutoreleasePool的clear(),又从_referenceCount=2变为_referenceCount=1,clear()中使用swap()方法保证了节点对象在当前的AutoreleasePool中仅存在一帧的时间,避免重复遍历
3、调用removeFromParent来删除对象,引用计数由1变为0,对象会被释放
cocos2dx的内存官方分为两种方式
1、手动方式:对象创建时_referenceCount赋值为1,调用retain(),_referenceCount+1,调用release(),_referenceCount-1,_referenceCount == 0时会被引擎回收掉释放内存
2、自动方式:在create()一个对象时,会调用autorelease()方法把该对象加入AutoreleasePool,在 mainLoop()方法中会调用当前AutoreleasePool中的clear()方法,clear()方法会遍历移除所有与该AutoreleasePool中的对象,并调用一次对象release()方法,如果这个对象_referenceCount == 0就会被引擎回收掉释放内存
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。