• cocos2d-x 内存管理浅析


    Cocos2d-x用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);
     

    非常简单方便,

    完全不需要关心内存的申请和释放,

    关于智能指针部分以后会对其做出分析。

  • 相关阅读:
    GJM : Unity的profiler性能分析【转载】
    GJM :Unity UI 之 UGUI 开发
    GJM:笔记记录
    GJM : UGUI 缩放循环拖动展示卡牌效果
    GJM :HTC Vive VRTK.Package 踩坑之路(一)
    GJM :Mono 介绍
    GJM :Unity3D 介绍
    GJM : C# 介绍
    GJM : 通用类型系统(Common Type System CTS)
    GJM :SQL Server中常用的SQL语句
  • 原文地址:https://www.cnblogs.com/hackerl/p/4791280.html
Copyright © 2020-2023  润新知