cocos2dx面试时最容易考的问题就是内存管理,我自己都被问的烦了,本文分析透彻,源码详尽,可以避免在这个问题上失分
cocos2dx的内存管理采用引用计数的策略,百度百科的引用计数解释如下:
通过源代码分析,作出以下总结:
1.Ref类中的_referenceCount成员变量用作引用计数
protected:
/// count of references
unsigned int _referenceCount;
2.Ref的构造函数为_referenceCount赋值为1
Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
3.retain() 执行++_referenceCount,release()执行--_referenceCount,并且当_referenceCount ==0时,释放对象.
void Ref::retain()
{
++_referenceCount;
}
void Ref::release()
{
--_referenceCount;
if (_referenceCount == 0)
{
delete this;
}
}
4.autorelease()会在自动释放池中,添加一个对象(向量尾部插入新对象),AutoreleasePool类中,使用向量容器vector<Ref*> _managedObjectArray保存对象。
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
}
std::vector<Ref*> _managedObjectArray;
5.通过create()创建的对象,会执行new(),init(),autorelease()几个函数,即对象被放入自动释放池,这类对象若不addchild()会在下一帧释放,若想保留可手动retain(),不需要时手动release()
Scene* Scene::create()
{
Scene *ret = new (std::nothrow) Scene();
if (ret && ret->init())
{
ret->autorelease();
return ret;
}
else
{
CC_SAFE_DELETE(ret);
return nullptr;
}
}
6.导演类的mainLoop()执行drawScene()画完一帧后,执行 PoolManager::getInstance()->getCurrentPool()->clear();clear()有2个功能:
(1)
std::vector<Ref*> releasings;
releasings.swap(_managedObjectArray);
用新向量releasings和向量_managectArray互换,将_managedObjectArray内容清空,长度清0,这么做是为了让自动释放池中的对象只自动执行一次release()判断是否释放
(2)
for (const auto &obj : releasings)
{
obj->release();
}
遍历自动释放池中的对象逐一调用一次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
}
7.addChild(Node *child)会执行child->retain();removechild()会执行child->release();
void Node::addChild(Node *child)
{
CCASSERT( child != nullptr, "Argument must be non-nil");
this->addChild(child, child->_localZOrder, child->_name);
}
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::insertChild(Node* child, int z)
{
_transformUpdated = true;
_reorderChildDirty = true;
_children.pushBack(child);
child->_localZOrder = z;
}
void pushBack(T object)
{
CCASSERT(object != nullptr, "The object should not be nullptr");
_data.push_back( object );
object->retain();
}
8.当对象的指针作为形参时,函数体内需retain()形参 (形参被其他指针保留了,所以需retain),
release被赋值指针(被赋值指针不需要使用原先指向的对象了,所以需release),再赋值.
void testFun(Ref* obj1)
{
obj1->retain();
obj2->release();
obj2 = obj1;
}
面试环节
问:cocos2d-x内存管理机制如何实现?
答:cocos2d-x使用引用计数机制实现内存自动管理。有一个基类Ref,Ref中通过referenceCount变量保存计数,构造函数中会把新建对象的计数赋值为1,Ref中retain可将计数自加,release可将计数自减,并且判断当计数为0时进行释放。新建出的节点加到父节点时会调用retain,从父节点移除时会调用release。有一个自动释放池类AutoreleasePool,其中有个autorelease函数,可将对象放到自动释放池的一个向量容器中,通过CREATE宏创建出来的函数都会执行autorelease操作。在每一帧结束时会调用自动释放池的clear函数,clear会将自动释放池中所有对象调用release函数。因此新建出来的对象其引用计数的变化过程为:创建出来构造函数赋值为1,加到父节点后变为2,所在帧结束时由于调用了clear又变为1这样一个过程。