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__, 上述介绍了一下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);
非常简单方便,
完全不需要关心内存的申请和释放,
关于智能指针部分以后会对其做出分析。