在cocos2dx-3.17.2
中的自动内存管理机制是借助引用计数来实现的。内存管理的实现基于Ref这个类,基本的原理就是其内部存在一个引用计数_referenceCount
,当这个引用计数为0的时候,就会被释放。引用计数通过retain
,release
来操作。
/**
* Ref is used for reference count management. If a class inherits from Ref,
* then it is easy to be shared in different places.
* @js NA
*/
class CC_DLL Ref
{
/* 省略 */
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;
/* 省略 */
}
};
流程图:
接下来从节点的创建到回收来说明Cocos的内存管理机制:
创建一个Node:
Node::create()
Node * Node::create()
{
Node * ret = new (std::nothrow) Node();
if (ret && ret->init())
{
ret->autorelease();
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}
Ref::aurorelease(): //将节点加入到自动释放池:
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
AutoreleasePool::addObject(Ref* object) :
void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
}
Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
/* 省略 */
}
创建节点后,节点的_referenceCount = 1。
可知,每一个创建的节点在不进行其他操作的情况下默认的_referenceCount == 1,_referenceCount 值的初始化在Ref类中。
添加节点到场景中:
Node::addChild(Node *child, int localZOrder, int tag):
/* "add" logic MUST only be on this method
* If a class want's to extend the 'addChild' behavior it only needs
* to override this method
*/
void Node::addChild(Node *child, int localZOrder, int tag)
{
/* 省略 */
addChildHelper(child, localZOrder, tag, "", true);
}
Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag):
void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)
{
/* 省略 */
this->insertChild(child, localZOrder);
/* 省略 */
}
void Node::insertChild(Node* child, int z):
// helper used by reorderChild & add
void Node::insertChild(Node* child, int z)
{
/* 省略 */
_children.pushBack(child);
/* 省略 */
}
Vector::pushBack(T object) : //节点创建时,就被添加了引用。这里的vector 是cocos重新定义的容器,不是std::vector
/** 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();
}
讲节点添加到场景中后,节点的_referenceCount = 2。
可知,再将节点添加到场景中,也就是作为另一个节点的子节点时,_referenceCount 会+1。
自动回收内存机制:
void Director::mainLoop(): //负责调用定时器,绘图,发送全局通知,并处理内存回收池。每一帧进行一次调用。
void Director::mainLoop()
{
/* 省略 */
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
void AutoreleasePool::clear(): //清空自动释放池中的对象,并且释放所有_referenceCount = 0 的节点内存
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
}
void Ref::release(): //如果节点_referenceCount = 0 ,释放节点内存
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
--_referenceCount;
if (_referenceCount == 0)
{
/* 省略 */
#if CC_REF_LEAK_DETECTION
untrackRef(this);
#endif
delete this;
}
}
可以看到,void AutoreleasePool::clear():里面对所有在自动释放池(_managedObjectArray)
中的对象都进行了一次release
操作,并把_managedObjectArray清空。
可知,当我们创建Node的时候
_referenceCount == 1,然后添加到场景之后 _referenceCount == 2,当场景绘制一帧以后系统会自动调用void AutoreleasePool::clear()函数,会遍历_managedObjectArray中所有的对象并执行其的release()函数,最后清空_managedObjectArray,此时节点的
_referenceCount == 1。
移除节点所在的场景:
void Director::replaceScene(Scene *scene):
void Director::replaceScene(Scene *scene)
{
/* 省略 */
if (_nextScene)
{
if (_nextScene->isRunning())
{
_nextScene->onExit();
}
_nextScene->cleanup();
_nextScene = nullptr;
}
/* 省略 */
_scenesStack.replace(index, scene);
}
void Node::cleanup(): //停止场景中所有节点的Action和schedule
void Node::cleanup()
{
/* 省略 */
// actions
this->stopAllActions();
// timers
this->unscheduleAllCallbacks();
for( const auto &child: _children)
child->cleanup();
}
Vector::replace(): //释放当前场景的内存,并保存要运行的场景
/** Replace value at index with given object. */
void replace(ssize_t index, T object)
{
CCASSERT(index >= 0 && index < size(), "Invalid index!");
CCASSERT(object != nullptr, "The object should not be nullptr");
_data[index]->release();
_data[index] = object;
object->retain();
}
可知,当我们移除场景时,首先会停止场景中所有节点的Action和schedule,然后释放当前场景的内存,并保存要运行的场景。
自动回收池作用在每帧结束的时候,在一帧开始之前,系统建立了一个内存回收池,在这一帧的过程中,当我们调用autorelease方法以后,我们的对象就会放到这个内存回收池中,当一帧结束的时候这个内存回收池就会释放掉,这个时候在内存回收池中的对象就会被release,也就是说当前内存回收池中的所有对象的referenceCount 都会 -1,如果这个时候referenceCount为0,就会释放节点的内存。如果节点引用计数不为0的话对象是不会被删除的。
下一帧开始的时候系统又会创建一个内存回收池,这个时候在上一次添加的对象这个时候是不会重新添加到这个内存回收池中的,在这个内存回收池中的对象是你在这一帧中调用了autorelease函数的节点。