cocos2dx 看上去很美的引用计数造成的内存泄露(一)——CCCallFunc对象

原创 2013年12月16日 11:49:26

所有的代码都已经屏蔽掉无关部分,仅展示对问题有实质影响的部分;


引用计数无需多言,以下简称RC。

先说RC的2个基本原则:

1、不直接new和delete对象,而是通过RC实现,RC为0,对象销毁。在cocos2dx中通过retain,release,autoRelease实现。

2、要使用一个对象,先retain,用完release,因为如果不这么做,说不定正在用的东西不知道被谁释放了。尤其是有autoRelease的存在。

如果大家都严格遵照这样2个原则,那么内存就不会泄露了。


以前也是这么认为,但是后来我发现这只是看上去很美。


深受android “毒害” 的我最近在项目中遇到了这么一个问题,内存占用只加不减,多切换几个场景,就会因为内存问题被系统杀掉。断点怒跟,发现是当前场景并没有调用析构。也就是说应该销毁的场景没销毁,造成了内存泄露。

原因是我自定义了一个button控件,如下:

class LCButton : public CCControl
{
public:
	void init(const char* normalPath  , const char* highlightPath , const char* disablePath , CCCallFunc *active , CCCallFunc *disactice);
	static LCButton* create(const char* normalPath  , const char* highlightPath , const char* disablePath , CCCallFunc *active , CCCallFunc *disactice , int priority = -127);

	void setDownSelector(CCCallFunc *call);
	void setUpSelector(CCCallFunc *call);
private:
	CCCallFunc *beginSelector;
	CCCallFunc *endSelector;
};
其中对于点击事件的回调,我是用CCCallfunc及其派生子类实现,因为我觉得CCCallfunc这个类存在的意义,就是拿来做回调。那么根据上面提到的RC原则,我就应该这么写:

void LCButton::setDownSelector(CCCallFunc *call)
{
	if(beginSelector)
	{
		beginSelector->release();
	}
	beginSelector = call ;
	CC_SAFE_RETAIN(beginSelector);
}
在这个对象的存在的生命周期内,这个回调都应该起作用,释放内存的工作应该放到析构进行,那么我就应该在析构对beginSelector进行释放:

LCButton::~LCButton(void)
{
	CC_SAFE_RELEASE(beginSelector);
}
然后在外部,使用这个组件的类,我也是这么处理的,create出来之后retain,析构release释放内存。

问题就在这里,看看CCCallfunc的create函数:

CCCallFunc * CCCallFunc::create(CCObject* pSelectorTarget, SEL_CallFunc selector) 
{
    CCCallFunc *pRet = new CCCallFunc();
    if (pRet && pRet->initWithTarget(pSelectorTarget)) { }
}
bool CCCallFunc::initWithTarget(CCObject* pSelectorTarget) {
    if (pSelectorTarget) 
    {
        pSelectorTarget->retain();
    }

    if (m_pSelectorTarget) 
    {
        m_pSelectorTarget->release();
    }
}
对于传入的this指针,做了一次retain。

那么这个callfunc对象,就对外部场景this保存了一次引用,当callfunc对象释放,释放this的引用。但是callfunc又被我这个button控件持有,button释放释放callfunc,但是button又被this持有,this释放释放button。

整理一下,就是  this->button->callfunc->this这么一个循环引用关系。相当于一个死锁,大家都释放不掉。


要解决这个问题,最简单的就是把对持有child的release提前,比如提前到onExit里面去做。但是这和设计理念有违背,因为释放内存这是应该是析构来做的事情。onExit只做清理。看cocos2dx源码里面的析构和onExit函数:

CCNode::~CCNode(void)
{
    CC_SAFE_RELEASE(m_pActionManager);
    CC_SAFE_RELEASE(m_pScheduler);
    CC_SAFE_RELEASE(m_pCamera);
    CC_SAFE_RELEASE(m_pGrid);
    CC_SAFE_RELEASE(m_pShaderProgram);
    CC_SAFE_RELEASE(m_pUserObject);
    CC_SAFE_RELEASE(m_pChildren);
    m_pComponentContainer->removeAll();
    CC_SAFE_DELETE(m_pComponentContainer);
}
void CCNode::onExit()
{
    this->pauseSchedulerAndActions();
    m_bRunning = false;
    arrayMakeObjectsPerformSelector(m_pChildren, onExit, CCNode*);    
}
也是在onExit里面做清理,析构做内存释放。


那么还有一种,就是在CCCallfunc里面不做retain,看CCMenu里面的create:

CCMenuItem* CCMenuItem::create(CCObject *rec, SEL_MenuHandler selector)
{
    CCMenuItem *pRet = new CCMenuItem();
    pRet->initWithTarget(rec, selector);
    pRet->autorelease();
    return pRet;
}
bool CCMenuItem::initWithTarget(CCObject *rec, SEL_MenuHandler selector)
{
    setAnchorPoint(ccp(0.5f, 0.5f));
    m_pListener = rec;
    m_pfnSelector = selector;
    m_bEnabled = true;
    m_bSelected = false;
    return true;
}
里面就没有对this进行retain处理。但是又觉得,有封装好了的回调类不用,又单独做一个,重复造轮子的行为。


而又看CCCallfunc,对this的一次retain在某些情况下又是必要的。比如做一个全局schedule调度的时候,this如果被释放了就什么都没有了。



最后还是选择了最容易实现的第一种方法,释放提前到onExit。


有更好的方法求告知。

cocos2d 一个坑爹的内存泄露

看以下事例代码: auto node1 = Sprite::create("CloseNormal.png"); node1->setName("node1"); auto action1 = Sc...
  • keep_moving_cqu
  • keep_moving_cqu
  • 2015年11月17日 22:45
  • 2257

cocos内存泄漏以及优化问题

这几天疯狂的赶进度 到了今天晚上突然服务端和美工告诉我 进度都卡住了。 实在无奈就上来更篇博客。 最近遇到几个好玩的事情,关于内存泄漏 要知道当时开发unity的时候很少去管内存这个东西,可能是u...
  • oZhongLou1234
  • oZhongLou1234
  • 2016年04月18日 21:58
  • 2070

cocos2dx 引用计数

转载自:http://www.cocoachina.com/bbs/read.php?tid=195219点击打开链接 我们经常的说每秒多少多少帧,其实这个帧需要多少时间不是固定的,这个需要看每帧我...
  • xi_mi_
  • xi_mi_
  • 2016年11月17日 20:25
  • 296

【cocos2dx】lua内存泄漏检测

近几天发现公司项目(cocos2dx arpg手游)战斗这块出现了内存泄漏,lua层和C++层都有泄漏,挂几分钟lua内存额外增加了50M,打算优先从lua入手去查询,因为针对C++层,我相信目前使用...
  • ghost0620
  • ghost0620
  • 2016年09月18日 15:04
  • 1544

VS 2012内存泄漏检测之 vld ( cocos2d-x 检测内存泄漏)

因为写项目难免会有内存泄漏情况,所有我们就找个方法来检测内存泄漏,在代码和工具比较之后,最终选择VLD强大的检测工具,具体方法如下: 首先下载安装 vld, 目前最新版是2.3   下载地址:htt...
  • yangjingui
  • yangjingui
  • 2013年08月05日 17:12
  • 4163

Cocos2dx中的引用计数和自动回收池

本文由qinning199原创,转载请注明:http://www.cocos2dx.net/?p=126 一、引用数 引用计数是c/c++工程中一种古老的内存管理方式。Ios SDK在NSAuto...
  • qinning199
  • qinning199
  • 2013年10月19日 00:44
  • 4696

自己动手写cocos2dx游戏引擎(六)——引用计数机制

这一节开始看看cocos2dx的内存管理。 cocos2dx的内存管理使用的是引用计数机制,当对象创建的时候引用计数为1,retian一次引用计数+1,release一次-1,为了是delete。 /...
  • xufeng0991
  • xufeng0991
  • 2015年12月15日 20:27
  • 741

Bullet(Cocos2dx)之内存泄露检测

编写的程序难免会有内存泄露,为了检测内存泄露,可以采取各种各样的措施, 今天向大家介绍一款windows下的内存检测工具Visual Leak Detector for Visual C++ 去官...
  • ctxdecs
  • ctxdecs
  • 2015年01月07日 14:46
  • 1449

cocos2dx 看上去很美的引用计数造成的内存泄露(一)——CCCallFunc对象

所有的代码都已经屏蔽掉无关部分,仅展示对问题有实质影响的部分; 引用计数无需多言,以下简称RC。 先说RC的2个基本原则: 1、不直接new和delete对象,而是通过RC实现,RC为0...
  • dinko321
  • dinko321
  • 2013年12月16日 11:49
  • 8688

cocos2dx 看上去很美的引用计数造成的内存泄露(二)——CCCallfuncO的参数

深受android “毒害” 的我又悲剧了。 这次自己做了一个类似listView之类的东西,每个item设置回调,就用了CCCallfunO,具体如下: void Myclass::callFu...
  • dinko321
  • dinko321
  • 2013年12月16日 13:10
  • 1796
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:cocos2dx 看上去很美的引用计数造成的内存泄露(一)——CCCallFunc对象
举报原因:
原因补充:

(最多只允许输入30个字)