cocos2dx 看上去很美的引用计数造成的内存泄露(一)——CCCallFunc对象
来源:互联网 发布:靠谱的水乳推荐 知乎 编辑:程序博客网 时间:2024/06/06 09:19
所有的代码都已经屏蔽掉无关部分,仅展示对问题有实质影响的部分;
引用计数无需多言,以下简称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。
有更好的方法求告知。
- cocos2dx 看上去很美的引用计数造成的内存泄露(一)——CCCallFunc对象
- cocos2dx 看上去很美的引用计数造成的内存泄露(二)——CCCallfuncO的参数
- 避免引用Context造成的内存泄露
- 看上去很美——关于SaaS的八大误区
- 由返回值为引用而造成的内存泄露
- 引用计数的cocos2dx对象内存管理和直接new/delete box2d对象内存管理冲突的解决方法
- cocos2dx CCHttpRequest里面的内存引用计数的故事
- Swift的自动引用计数->解决内存泄露
- 定时器造成的内存泄露
- 关于cocos2dx CCCallfunc对象的传参 (2.x)
- MRC内存管理(一)普通的引用计数
- Android内存泄露问题(一)之context的引用
- weak_ptr 弱引用打破智能指针中循环引用造成的内存泄露
- 顺丰嘿客:看上去很美的O2O乌托邦
- Java Card——看上去很美
- Silverlight - 绑定造成的内存泄露
- 可能造成内存泄露的东西
- Android内存泄露造成的OOM问题
- Save Time with minicom macros
- EM算法
- linux驱动开发相关头文件说明
- oracle主机名或监听端口号查看 修改
- 坚持写技术博客,不抛弃不放弃!(献给2013CSDN博客之星评选活动)
- cocos2dx 看上去很美的引用计数造成的内存泄露(一)——CCCallFunc对象
- webconfig中appSettings和connectionStrings的使用
- ubuntu中ANT的安装和配置
- VS2010“general error c101008d”错误的处理
- 街头篮球解封器,源码代码分享 - 八周年活动
- ios coredate使用(中篇)
- 编程语言编年史(英文原版)
- 设置QFrame的背景图片并不影响其子控件的效果
- CString的Trim()方法和TrimStrat()和TrimEnd()方法