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

非常简单方便,

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

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

转载于:https://www.cnblogs.com/hackerl/p/4791280.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值