cocos2d-x内存

来源:互联网 发布:c语言华氏度 编辑:程序博客网 时间:2024/05/22 14:36
http://blog.sina.com.cn/s/blog_a502f1a30101hh6h.html
转载,好文章
在看这个之前,你先要了解onEnter , onExit 和 构造函数,析构函数在调用顺序上面的区别
总的来说,顺序如下

构造函数
{
}
onEnter
{
}
onExit
{
}
析构函数
{
}

讲这个之前需要讲一些在实践中遇到的问题。前段时间在项目中fix掉两个和内存相关的bug,然后就对这两个方法存在有一些新的理解。

1 我们用的cocos2d-x的版本是cocos2d-1.0.1-x-0.12.0,那时候还没有CCListView.cpp这个列表控件,所以项目组就自己写了这个控件。我们知道列表控件的需要放图片或者文字等等信息,所以必须写成虚类。如下

struct IListItem:
{
    //how we draw this item
    virtual cocos2d::CCNode *createNode(float width) = 0;
    virtual ~IListItem() {}
};

对应的Item需要继承IListView才能来编写这个接口,如下例子

class FightItem : public IListItem
{
public:

FightItem(const DailyProperty &dp);
~FightItem();
virtual cocos2d::CCNode *createNode(float width);
virtual void onExit();
void selected();
void unselected();

static FightItem *itemWithFight(const DailyProperty &dp);
const DailyProperty &getDailyProperty() {return m_dp;}

private:
cocos2d::CCNode *m_node;
};

看完这个接口,如果我们申明了FightItem *item(下面都是用item来指FightItem实例),那么item的释放我们只能使用delete了。本来用delete也没什么关系,随着需求的变化,我们需要用到一些高级的方法,例如在FigthItem里面。这时候我们就需要调用CCCallFunc::actionWithTarget(this,callfunc_selector(FigthItem::change) 类似于这样的方法。这里就要求FightItem继承CCObject才能使用callfunc_selector。所以FightItem的声明被改成了

class FightItem : public IListItem, cocos2d::CCObject
{
public:

FightItem(const DailyProperty &dp);
~FightItem();
virtual cocos2d::CCNode *createNode(float width);
virtual void onExit();
void selected();
void unselected();

static FightItem *itemWithFight(const DailyProperty &dp);
const DailyProperty &getDailyProperty() {return m_dp;}

private:
cocos2d::CCNode *m_node;
void change();
};

到这里,内存管理就开始出现问题了,就是release和delete的混用的问题,FightItem现在是由引用计数管理着,而CCListView里面就用delete把FightItem释放了,然后引用计数仍然认为FightItem还没释放,这里就是double free,释放两次的错误了。

到这里,需要总结一句,框架引入了引用计数做内存管理的时候,尽量不用混用delete,否则其他人不知道混用了,就会出这种问题。

很自然的,CCListView里面也采用 FightItem * item; item->release(),这样的释放机制,问题就决绝了。接着,下一波问题出现了。
再次出现的是泄露问题了。

我们需要在FightItem 里面播放一个动画,这时我们有个地方就用了如下的方法

CCScheduler::sharedScheduler()->scheduleSelector(
          schedule_selector(FightItem::showTimeDescription), this, 0.5f, false);

好的,现在我们的析构函数这样写

FightItem::~FightItem()
{
CCScheduler::sharedScheduler()->unscheduleSelector(
schedule_selector(FightItem::showTimeDescription), this);
    if(m_node) m_node->release();
}

这样看不出来有什么问题是吧,不过在偶然的情况下,我发现这个析构函数根本不执行。真的很偶然,这样的泄露确实是很难找出来的,所以先建立起这个意识确实很重要,等到项目代码多了出这种问题,就要整个项目排查了。下面分析一下为什么不执行。

FightItem创建的时候count[引用计数]为1, 执行
CCScheduler::sharedScheduler()->scheduleSelector(
          schedule_selector(FightItem::showTimeDescription), this, 0.5f, false);
的时候,引用计数为2。说明CCScheduler占用的了FightItem。按照原来的删除过程,CCListView执行item->release(),FightItem引用计数变为1,还被CCScheduler占用。按照C++的机制,delete没有执行,~FightItem也没有进入所以
CCScheduler::sharedScheduler()->unscheduleSelector(
schedule_selector(FightItem::showTimeDescription), this);
函数虽然写在里面了,可是没有执行的机会,这样item永远没有释放的机会。听起来像不像数据库里面的死锁啊。

终于可以开始写主题了,这时候我们就需要虚函数onExit了,分写修改如下
struct IListItem:public cocos2d::CCObject
{
    //how we draw this item
    virtual cocos2d::CCNode *createNode(float width) = 0;
    virtual ~IListItem() {}
virtual void onExit() {}
};

FightItem::~FightItem()
{
    if(m_node) m_node->release();
}

void FightItem::onExit()
{
CCScheduler::sharedScheduler()->unscheduleSelector(
schedule_selector(FightItem::showTimeDescription), this);
}

这时候CCListView执行item->release()改成这样
item->onExit()
item->release();
onExit()把CCScheduler的引用清掉,接着CCListView执行release(),这时候引用计数为0,系统就会执行delete了,这时候~FightItem()就会进入了。

这时候就可以得出最后结论了,onEnter,onExit是配合引用计数机制存在的,总的来说,所有造成本对象被其他对象引用的操作,都放在onEnter里面,所有去掉本对象被其他对象引用的操作都放在onExit里面,这样就能保证本对象在释放的时候能够成功了。

所以,我们一般都是写成如下的规范,就很少出现内存泄露问题了

void CCImageBtn::onEnter()
{
CCNode::onEnter();
    CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, m_touchPriority, true);
}

void CCImageBtn::onExit()
{
    CCTouchDispatcher::sharedDispatcher()->removeDelegate(this);
    CCNode::onExit();
}



关于引擎内存管理
看完内存的部分内核后,感觉这个内存管理好鸡肋啊,相当没有用处。还不如自己做new和release。
记得在层析构函数中增加一个cclog。要保证每个层在切换后及时的释放。这个很重要。
原创粉丝点击