由于C++目前未提供完整的垃圾回收机制,cocos2d-x必须手动来管理内存。3.6版本里,cocos2d-x使用了一种类oc的内存管理机制,利用引用计算来控制对象的释放。
在cocos2d-x中,几乎所有的对象都继承自Ref类,而各对象的引用计数也都在Ref类中维护,Ref类结构如下(代码1):
class CC_DLL Ref对象每retain一次引用计数加1,每release一次引用计数减1,当引用计算减到0时,释放资源。
{
public:
void retain();
void release();
Ref* autorelease();
unsigned int getReferenceCount() const;
protected:
unsigned int _referenceCount;
friend class AutoreleasePool;
...
};
我们可以手动retain,release控制对象的引用计数,也可以通过autorelease来自动化管理对象引用计数。
所谓的自动化管理,就是调用create方法创建Ref对象后,无需手动调用release方法,系统在适当的时候自动调用release方法,这种方便主要是针对 node类型的对象。
以Sprite为例,首先创建一个Sprite对象,create函数实现如下(代码2):
Sprite* Sprite::create(const std::string& filename)成功的话,返回一个autorelease对象,autorelease函数实现如下(代码3):
{
Sprite *sprite = new (std::nothrow) Sprite();//所有Ref对象引用计数,初始值为1
if (sprite && sprite->initWithFile(filename))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
void AutoreleasePool::addObject(Ref* object){ _managedObjectArray.push_back(object);}autorelease只是将对象添加到一个全局的对象池中。其中_managedObjectArray是一个std::vector<Ref*>类型的容器(注,是标准库的容器,非cocos容器)。
至此,对象创建完成。
第二步,将sprite添加到helloworld场景当中。代码如下(代码4):
bool HelloWorld::init()
{
...
this->addChild(sprite, 0);
return true;
}
void Node::addChild(Node *child, int zOrder){ CCASSERT( child != nullptr, "Argument must be non-nil"); this->addChild(child, zOrder, 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){ ... this->insertChild(child, localZOrder); ...}
void Node::insertChild(Node* child, int z){ _transformUpdated = true; _reorderChildDirty = true; _children.pushBack(child);//注:这里的_children窗口为cocos2d::Vector<Node*>类型,非std::Vector child->_localZOrder = z;}
void pushBack(T object) { CCASSERT(object != nullptr, "The object should not be nullptr"); _data.push_back( object ); object->retain();//引用计数为2 }可以看到,在sprite添加到父节点时,子结点会有一次retain()的操作,retain()完后引用计数为2。
在一帧绘制完毕后,director将会对对象池里的对象进行一次release,代码如下(代码5):
void DisplayLinkDirector::mainLoop()clear函数中,将对对象池中的所有对象引用计算减1。
{
...
drawScene();//sprite引用计数为2
PoolManager::getInstance()->getCurrentPool()->clear();//sprite引用计数为1
...
}
可以看到,上述过程中,sprite对象引用计数一直大于0,所以一直未销毁(程序的逻辑上确实也是如此)。
那么问题来了,sprite什么时候销毁呢?当引用计数减到0的时候(这是废话),有几种情况会使的sprite的引用计数减1:
1)场景销毁的时候_runningScene->release();
2)场景重新运行的时候
3)sprite从父节点remove掉的时候
前2种情况是对所有的对象池里的数据进行引用减1,第3种是对特定的remove对象引用减1。代码如下(代码6):
void Node::removeChild(Node* child, bool cleanup /* = true */)当对象引用计数为0的时候,对象会释放自身的资源,并且会删除对象池里的引用(如果存在的话)。
{
// 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)
{
...
// set parent nil at the end
child->setParent(nullptr);
_children.erase(childIndex);
}
iterator erase(ssize_t index)
{
CCASSERT(!_data.empty() && index >=0 && index < size(), "Invalid index!");
auto it = std::next( begin(), index );
(*it)->release();//减1操作在此!
return _data.erase(it);
}
总结一下:cocos的autorelease(自动释放)机制,即:
对于node的子类,将new与delete操作与create(),addchild(),removechild()等操作绑定。 当对象从UI上移除也表示着其资源得到释放。