cocos2d-x 从onEnter、onExit、 引用计数 谈内存泄露问题

///////////////////////////////////
//author : zhxfl
//date   : 2013.8.29
//email  : 291221622@qq.com
///////////////////////////////////
 
在看这个以前,你先要了解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() {}
};
View Code
 
对应的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;
};
View Code
 
看完这个接口,若是咱们申明了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();
};
View Code
 
到这里,内存管理就开始出现问题了,就是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);
View Code
 
好的,如今咱们的析构函数这样写
 
FightItem::~FightItem()
{
    CCScheduler::sharedScheduler()->unscheduleSelector(
        schedule_selector(FightItem::showTimeDescription), this);
    if(m_node) m_node->release();
}
View Code
 
这样看不出来有什么问题是吧,不过在偶然的状况下,我发现这个析构函数根本不执行。真的很偶然,这样的泄露确实是很难找出来的,因此先创建起这个意识确实很重要,等到项目代码多了出这种问题,就要整个项目排查了。下面分析一下为何不执行。
 
FightItem建立的时候count[引用计数]为1, 执行
CCScheduler::sharedScheduler()->scheduleSelector(
          schedule_selector(FightItem::showTimeDescription), this, 0.5f, false);
的时候,引用计数为2,加入CCListView引用计数为3(注意当引用计数为1的时候,会被系统直接释放掉)。说明CCScheduler占用的了FightItem。按照原来的删除过程,CCListView执行item->release(),FightItem引用计数变为2,还被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);
}
View Code
这时候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();
}
View Code
相关文章
相关标签/搜索