• 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
  • 相关阅读:
    由于可能不会将凭据发送到远程计算机,因此将不会进行连接。若要获得协助,请与您的系统管理员联系。
    URL编码与解码
    linux服务器openjdk11环境easypoi导出excel报错(class sun.font.CompositeFont cannot be cast to class sun.font.PhysicalFont)
    解决Drools中文乱码
    CISSP-什么是安全冠军以及您为什么需要安全冠军
    windows环境中JDK环境变量配置
    【Django】定时任务
    【Django】权限之has_perm
    一些连接
    数据库
  • 原文地址:https://www.cnblogs.com/zhxfl/p/3288510.html
Copyright © 2020-2023  润新知