一、cc.Node
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到底是啥呢?还是从源码入手,我们找到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;
三、AutoreleasePool和PoolManager
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就会被引擎回收掉释放内存