【深入了解cocos2d-x 3.x】UI树(2)——UI树的内存管理机制

【深入了解cocos2d-x 3.x】UI树(2)——UI树的内存管理机制,第1张

概述上篇文章分析了什么是UI树,以及UI树的使用方法,这节会重点分析UI树的内存管理机制以及如何利用UI树对游戏中的UI内存进行合理的管理。 说到UI树的内存管理机制,就不得不提cocos2d-x的内存管理机制——引用计数了,相信只要不是初学者都已经理解了这一块了,这里还是对cocos2d-x的内存管理机制做一个大概的介绍吧。 cocos2d-x采用的是引用计数法作为其内存管理的方法,引用计数法的核心

上篇文章分析了什么是UI树,以及UI树的使用方法,这节会重点分析UI树的内存管理机制以及如何利用UI树对游戏中的UI内存进行合理的管理。


说到UI树的内存管理机制,就不得不提cocos2d-x的内存管理机制——引用计数了,相信只要不是初学者都已经理解了这一块了,这里还是对cocos2d-x的内存管理机制做一个大概的介绍吧。

cocos2d-x采用的是引用计数法作为其内存管理的方法,引用计数法的核心思想为,当某个类需要引用变量x时,需要增加一个变量x的引用计数,当这个类不需要变量x时,需要减少一个变量x的引用计数。这样,谁引用,谁释放,引用和释放成对出现,就可以避免掉内存泄露的问题了。cocos2d-x对这一方法还有一个扩展,就是自动延迟释放机制,就是,如果存在一个变量x,它在函数的一帧上都需要用,但是下一帧,变量x就可以被释放掉,如果我们手动的在下一帧上释放x, *** 作其他会非常麻烦,也不直观,cocos2d-x提供了一个函数:autorelease,这个函数将会把对象加入到默认的自动释放池中,在一帧结束,引擎将自动清理自动释放池中的变量内存,这样就非常方便了。

cocos2d-x有一个不成文的规定,指针的创建一般不会用new直接创建,而是通过一个方法:create来创建,这个方法已经被引擎封装成一个宏定义了:CREATE_FUNC,下面是这个宏定义的实现:

#define CREATE_FUNC(__TYPE__) \static __TYPE__* create() \{ \    __TYPE__ *pRet = new __TYPE__(); \    if (pRet && pRet->init()) \    { \        pRet->autorelease(); \        return pRet; \    } \    else \    { \        delete pRet; \        pRet = NulL; \        return NulL; \    } \}

其他函数我们不分析,可以看到它在其中首先new了这个类__TYPE__, 这时候new出来的对象的引用计数为1,然后初始化完成后,这里执行了autorelease,这时候引用计数仍然为1,但是引擎将其加入了自动释放池,在这一帧结束的时候,这个对象的引用计数将变为0,引用计数为0的对象将会被释放掉。


上述很啰嗦的介绍了一下cocos2d-x的内存管理机制,现在进入正文了,当一个节点被加入到UI树中,它的引用计数将会有怎么样的变化呢?下面是Node的addChild的源码分析(addChild中真正的实现在addChildHelper中,下文忽略了不相关的代码):

voID Node::addChildHelper(Node* child,int localZOrder,int tag,const std::string &name,bool setTag){    this->insertChild(child,localZOrder);        if (setTag)        child->setTag(tag);    else        child->setname(name);        child->setParent(this);    child->setorderOfArrival(s_globalOrderOfArrivaL++);}

可以看到真正的实现是在insertChild这个函数中的,我们继续尾随进去:
voID Node::insertChild(Node* child,int z){    _transformUpdated = true;    _reorderChildDirty = true;    _children.pushBack(child);    child->_setLocalZOrder(z);}
好艰辛,终于看到了什么,这里将child加入到了_children中,_children是什么呢? 看它的声明
Vector<Node*> _children;
注意,这是一个大写V开头的Vector,说明这是cocos2d-x自己实现的可变数组,这个数组实际上和std标准库中的数组的实现差不多,标准库的算法可以完美的应用在这个数组上,这个数组与std::vector的最大区别就是引入了引用技术机制。在pushBack中,究竟做了些什么呢?
    voID pushBack(T object)    {        CCASSERT(object != nullptr,"The object should not be nullptr");        _data.push_back( object );        object->retain();    }


没错,重点在于这里
object->retain();

它对于添加进来的对象都增加了引用,这样就说明,所有被加入UI树中的节点都会被UI树保持强引用。


接下来对于removeXXXX函数进行分析,就挑选removeChild函数进行分析吧(其他函数也大同小异)

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 );}

而这个函数最终调用的是detachChild函数,来继续跟踪进去吧(忽略的无关代码)

voID Node::detachChild(Node *child,ssize_t childindex,bool doCleanup){    // set parent nil at the end    child->setParent(nullptr);    _children.erase(childindex);}

这里的重点代码就是
_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();        return _data.erase(it);    }
没错,它执行了下面这句代码:
(*it)->release();
减少了对象的引用计数,这样就能将UI从UI树中分离并且不会造成内存泄露了。


当然,这样做的好处还不止这些,试想如下代码

Scene* s = Scene::create();Director::getInstance()->runWithScene(s);Layer* l = Layer::create();s->addChild(l);.... 若干帧后s->removeChild(l);


是否会造成内存泄露?

答案是不会,而且这样写出来的代码,我们并不需要关心内存的分配问题,引擎会自动帮我们申请内存,并且在不需要的时候,自动将内存回收。这似乎是一个非常好的解决方案,但是也有一些不足。

试想如下使用场景,现需要将上述的l节点与s节点中间增加一个层m,m是s场景的子节点,也是l层的父节点,这时候应该怎么做呢?要知道在removeChild之后,l层的内存已经被释放掉了。似乎没有什么解决方法了,看下文:

Scene* s = Scene::create();Director::getInstance()->runWithScene(s);Layer* l = Layer::create();l->setTag(1);s->addChild(l);.... 若干帧后auto l = s->getChildByTag(1);l->retain();s->removeChild(l);Layer* m = Layer::create();s->addChild(m);m->addChild(l);l->release();

这样提前将l取出来增加一个引用计数就可以避免l的内存被UI树释放掉了,但是值得注意的是,retain方法必须与release方法对应出现,否则会造成内存泄露。但是开发者往往会忘记写后面的release从而造成内存泄露,那么怎么避免这样的情况出现呢,答案是:智能指针。看下面的代码
Scene* s = Scene::create();Director::getInstance()->runWithScene(s);Layer l = Layer::create();l->setTag(1);s->addChild(l);.... 若干帧后RefPtr<Node*> l = s->getChildByTag(1);s->removeChild(l);Layer m = Layer::create();s->addChild(m);m->addChild(l);
非常简单方便,完全不需要关心内存的申请和释放,关于智能指针部分以后会对其做出分析。 总结

以上是内存溢出为你收集整理的【深入了解cocos2d-x 3.x】UI树(2)——UI树的内存管理机制全部内容,希望文章能够帮你解决【深入了解cocos2d-x 3.x】UI树(2)——UI树的内存管理机制所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1051352.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-25
下一篇 2022-05-25

发表评论

登录后才能评论

评论列表(0条)

保存