(如有错误请联系我更正,以免误导他人!)
我们知道,cocos2dx中使用了引用计数的方式去管理内存,不需要我们手动delete的去释放内存。那么cocos2dx中是怎么实现引用计数的内存管理方式的呢?
cocos2dx中的内存管理用到了两个工具:引用计数器(Ref)、自动回收池(AutoreleasePool)。
引用计数器(Ref):
①Ref成员变量_referenceCount:引用计数,用来记录对象被引用的次数。
②retain函数:使引用计数加1。
③release函数:使引用计数减1,并当引用计数为0时回收对象。
④autorelease函数:将对象加入到自动回收池。要使用cocos2dx的自动回收池必须使用该方法。
由以上几点我们可知,要使用cocos2dx的内存管理机制,我们必须继承Ref类(如Scene、Layer、Sprite等)并调用autorelease函数。
自动回收池(AutoreleasePool):
_managedObjectArray,AutoreleasePool的成员变量。它的类型是Vector,用来管理添加到自动回收池中的对象。对象调用autorelease函数就把该对象添加到_managedObjectArray中。在游戏主循环中,cocos2dx每一帧遍历_managedObjectArray中的对象,并使它们都调用release函数。
小伙伴可能会问了:每一桢都调用release函数,那所有的对象不是都会很快被回收掉么。答案是否定的。因为每一帧_managedObjectArray都会被清空。我们在创建对象时调用autorelease函数(一般用法,特殊用法我们这里不讨论),那么我们创建对象这个操作算作该对象的一次引用,而这个引用的生命周期仅仅是在我们创建对象语句所在的大括号里面。所以自动回收池要在下一帧回收这个引用。而我们要回收创建对象以后对该对象的引用,就要通过手动的调用release函数或者通过调用其它在内部调用了release函数的函数(一般cocos2dx内部自动release不需我们手动调用)。
下面我们通过几段代码来具体认识一下
<span style="font-size:18px;"><div>Sprite* sp = nullptr; bool LGameScene::init() { if (!Layer::init()) return false; sp = Sprite::create(); scheduleOnce(schedule_selector(LGameScene::func), 2.0f); return true; }</div><div> </div><div>void LGameScene::func(float delt) { sp->setPosition(100, 300); }</div></span>我们有个全局变量sp,在init函数里使用create对它进行初始化。并且我们使用了scheduleOnce在2秒后调用func函数设置sp的position。运行结果如下:
报错了。这是为什么呢?
在cocos2dx中使用create方法会在内部调用autorelease函数。也就是说,在init方法中创建了Sprite对象sp之后,自动回收池会马上让该对象调用release函数,这时这个对象的引用计数(初始化为1)会马上变成0,马上被release清理掉。这意味着这个对象创建之后马上被销毁了,所以在func中再使用它就出错了。
下面我们修改init方法如下:
<span style="font-size:18px;">bool LGameScene::init() { if (!Layer::init()) return false; sp = Sprite::create(); sp->retain(); scheduleOnce(schedule_selector(LGameScene::func), 2.0f); return true; }</span>我们加了一行代码:sp->retain();这时,我们的引用计数加1变为2。自动回收池使sp->release之后sp的引用计数为1,不被回收。所以这时运行不报错了。
上面这些内容有许多博文中都详细的介绍。但是下面的内容break百度了许久也没找到答案。最后经过几个小时的奋战把答案找出来了,在这里与大家分享下。 问题:cocos2dx中当节点被回收时,它的子节点的回收是在哪里实现的?
一个节点要变为另一个节点的子节点那么必然会调用addChild函数。在addChild函数内部调用了retain函数使节点不会被自动回收池回收。所以经过addChild和自动回收池后,子节点的引用计数为1(我们没有手动调用retain函数的情况下。如果调用了retain要及时调用release,否则节点将不被自动回收。),那么这时,我们要回收子节点只需让子节点调用一次release函数即可。
那么,结合上面的知识,我们很容易知道,我们只要在父节点回收时遍历子节点并让子节点调用release函数即可,即一个循环结构。break查看源码发现,Node::removeAllChildren函数内部有这个遍历回收过程。然后break找到Node的析构函数,发现析构函数内部即没有调用removeAllChildren又没有刚才提到的循环结构。
最后,break注意到Node的成员变量 Vector<Node*> _children。_children中储存了所有的子节点,要遍历子节点离不开_children。而Node析构时会隐式调用Vector的析构方法来清除_children。而这个Vector是cocos2dx自带的,并不是C++ STL中的Vector。于是,break找到Vector的析构函数,发现它在内部调用了clear函数,clear函数代码如下:
<span style="font-size:18px;">void clear() { for( auto it = std::begin(_data); it != std::end(_data); ++it ) { (*it)->release(); } _data.clear(); }</span>!!!子节点的遍历回收就在里了有木有!!!隐藏的好深有木有!