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

///////////////////////////////////
//author : zhxfl
//date   : 2013.8.29
//email  : 291221622@qq.com
//Adress : http://www.cnblogs.com/zhxfl/p/3288510.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() {}
};
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

 

posted on 2013-08-29 10:31  zhxfl  阅读(725)  评论(0编辑  收藏  举报

导航