07 Cocos2d-x内存管理

引用计数的由来
cocos2d-x 的世界是基于 CCObject 类构建的,其中的每个元素:层、场景、精灵等都是一个个 CCObject 的对象。所以 内存管理的本质就是管理一个个 CCObject。作为一个 cocos2d 的 C++ 移植版本,在它之前有很多其它语言的 实现,从架构层次来说,这与语言的实现无关(比如 CCNode 的节点树形关系,其它语言也可以实现,如果是内存方便,C# 等更是无需考虑),但就从内存管理方面来说,参考了 OC (Objective-C) 的内存管理实现。
一个简单的自动管理原则:CCObject 内部维护着一个引用计数,引用计数为 0 就自动释放 ~(如果么有直接做如 delete 之类的操作)。那么此时可以预见,管理内存的实质就是管理这些 "引用计数" 了!使用 retain 和 release 方法对引用计数进行操作!
为什么要有自动释放池 及其作用
我们知道 cocos2d-x 使用了自动释放池,自动管理对象,知其然!其所以然呢?为什么需要自动释放池,它在整个框架之中又起着什么样的作用!在了解这一点之前,我们需要 知道 CCObject 从创建之初,到最终销毁,经历了哪些过程。在此,一叶总结以下几点:
1.刚创建的对象,而 为了保证在使用之前不会释放(至少让它存活一帧),所以自引用(也就是初始为1)
2.为了确定是否 实际使用,所以需要在一个合适的时机,解除自身引用。
3.而这个何时的时机正是在帧过度之时。
4.帧过度之后的对象,用则用矣,不用则弃!
5.由于已经解除了自身引用,所以它的引用被使用者管理(一般而言,内部组成树形结构的链式反应,如 CCNode)。
6.链式反应,也就是,如果释放一个对象,也会释放它所引用的对象。
上面是一个对象的大致流程,我们将对象分为两个时期,一个是刚创建时期,自引用为 1(如果为 0 就会释放对象,这是基本原则,所以要大于 0) 的时期,另一个是使用时期。上面说到,为了保证创
建时期的对象不被销毁,所以自引用(并没有实际的使用)初始化为 1,这就意味着我们需要一个合适的时机,来解除这样的自引用。
何时?在帧过度之时!(这样可保证当前帧能正确使用对象而没有被销毁。)怎么样释放?由于是自引用,我们并不能通过其它方式访问到它,所以就有了自动释放池,我们 变相的将“自引用”转化“自动释放池引用”,来标记一个 “创建时期的对象”。然后在帧过度之时,通过自动释放池管理,统一释放 “释放池引用”,也就意味着,去除了“自身引用”。帧过度之后的对象,才是真正的被使用者所管理。 下面我们用代码来解释上述过程。
通常我们使用 create(); 方法来创建一个自动管理的对象,而其内部实际操作如下:
 
 
  1. //初始化一个对象
  2. staticCCObject*create()
  3. {
  4. //newCCObject对象
  5. CCObject*pRet=newCCObject();
  6. if(pRet&&pRet->init())
  7. {
  8. //添加到自动释放池
  9. pRet->autorelease();
  10. returnpRet;
  11. }
  12. else
  13. {
  14. deletepRet;
  15. pRet=0;
  16. return0;
  17. }
  18. }
  19. //我们看到初始化的对象自引用m_uReference=1
  20. CCObject::CCObject(void)
  21. :m_uAutoReleaseCount(0)
  22. ,m_uReference(1)//whentheobjectiscreated,thereferencecountofitis1
  23. ,m_nLuaID(0)
  24. {
  25. staticunsignedintuObjectCount=0;
  26. m_uID=++uObjectCount;
  27. }
  28. //标记为自动释放对象
  29. CCObject*CCObject::autorelease(void)
  30. {
  31. //添加到自动释放池
  32. CCPoolManager::sharedPoolManager()->addObject(this);
  33. returnthis;
  34. }
  35. //继续跟踪
  36. voidCCPoolManager::addObject(CCObject*pObject)
  37. {
  38. getCurReleasePool()->addObject(pObject);
  39. }
  40. //添加到自动释放池的实际操作
  41. voidCCAutoreleasePool::addObject(CCObject*pObject)
  42. {
  43. //内部是由一个CCArray维护自动释放对象,并且此操作会使引用+1
  44. m_pManagedObjectArray->addObject(pObject);
  45. //由于初始化引用为1,上面又有操作,所以引用至少为2(可能还被其它所引用)
  46. CCAssert(pObject->m_uReference>1,"referencecountshouldbegreaterthan1");
  47. ++(pObject->m_uAutoReleaseCount);
  48. //变相的将自身引用转化为释放池引用,所以减1
  49. pObject->release();//norefcount,inthiscaseautoreleasepooladded.
  50. }
上面便是通过 create() 方法创建对象的过程。文中说到,一个合适的时机,解除自身引用(也就是释放池引用),那这又是在何时进行的呢?程序的运行有一个主循环,控制着每一帧的操作,在每一帧画面画完之时会自动调用 CCPoolManager::sharedPoolManager()->pop(); 方法 ( 具体可参见文章Cocos2d-x 程序是如何开始运行与结束的 ,这里我们只要知道每一帧结束都会调用 pop() 方法),来自动清理 创建时期 的引用。现在我们就来看看 pop() 的方法实现:
  
  
  • voidCCPoolManager::pop()
  • {
  • if(!m_pCurReleasePool)
  • {
  • return;
  • }
  • //当前释放池个数,pop使用栈结构
  • intnCount=m_pReleasePoolStack->count();
  • //释放池当中存放的都是创建时期对象,此时解除释放池引用
  • m_pCurReleasePool->clear();
  • //当前释放池,出栈,在这里可以看到判断nCount是否大于1,文后将会对此做具体说明
  • if(nCount>1)
  • {
  • m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
  • //if(nCount>1)
  • //{
  • //m_pCurReleasePool=m_pReleasePoolStack->objectAtIndex(nCount-2);
  • //return;
  • //}
  • m_pCurReleasePool=(CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount-2);
  • }
  • /*m_pCurReleasePool=NULL;*/
  • }
  • //释放池引用清理工作
  • voidCCAutoreleasePool::clear()
  • {
  • //如果释放池存在创建时期的对象
  • if(m_pManagedObjectArray->count()>0)
  • {
  • //CCAutoreleasePool*pReleasePool;
  • #ifdef_DEBUG
  • intnIndex=m_pManagedObjectArray->count()-1;
  • #endif
  • CCObject*pObj=NULL;
  • CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray,pObj)
  • {
  • if(!pObj)
  • break;
  • --(pObj->m_uAutoReleaseCount);
  • //(*it)->release();
  • //delete(*it);
  • #ifdef_DEBUG
  • nIndex--;
  • #endif
  • }
  • //移除释放池对创建时期对象的引用,从而使对象交由使用者全权管理
  • m_pManagedObjectArray->removeAllObjects();
  • }
  • }
  • 到这里,自动释放池的作用也就完成了! 可以说创建的对象在一帧 (但有特殊情况,下一段说明) 之后就完全脱离了 自动释放池的控制,自动释放池,对对象的管理也就在 创建时期起着作用!之后便交由使用者管理,释放。
    对"释放池"的管理说明
    我们知道了释放池管理着 创建时期 的对象,那么对于释放池本身是如何管理的?我们知道对于释放池,只需要有一个就已经能够满足我们的需求了,而在 cocos2d-x 的设计中,使用了集合管理 一堆 释放池。而在实际,它们又发挥了多大的用处?
    //释放池管理接口
      
      
  • classCC_DLLCCPoolManager
  • {
  • //释放池对象集合
  • CCArray*m_pReleasePoolStack;
  • //当前操作释放池
  • CCAutoreleasePool*m_pCurReleasePool;
  • //获取当前释放池
  • CCAutoreleasePool*getCurReleasePool();
  • public:
  • CCPoolManager();
  • ~CCPoolManager();
  • voidfinalize();
  • voidpush();
  • voidpop();
  • voidremoveObject(CCObject*pObject);
  • //添加一个创建时期对象
  • voidaddObject(CCObject*pObject);
  • staticCCPoolManager*sharedPoolManager();
  • staticvoidpurgePoolManager();
  • friendclassCCAutoreleasePool;
  • };
  • //我们从addObject开始看起,由上文可以addObject是由CCObject的autorelease自动调用的
  • voidCCPoolManager::addObject(CCObject*pObject)
  • {
  • getCurReleasePool()->addObject(pObject);
  • }
  • CCAutoreleasePool*CCPoolManager::getCurReleasePool()
  • {
  • //如果当前释放池为空
  • if(!m_pCurReleasePool)
  • {
  • //添加一个
  • push();
  • }
  • CCAssert(m_pCurReleasePool,"currentautoreleasepoolshouldnotbenull");
  • returnm_pCurReleasePool;
  • }
  • voidCCPoolManager::push()
  • {
  • CCAutoreleasePool*pPool=newCCAutoreleasePool();//ref=1
  • m_pCurReleasePool=pPool;
  • //像集合添加一个新的释放池
  • m_pReleasePoolStack->addObject(pPool);//ref=2
  • pPool->release();//ref=1
  • }
  • 从 addObject 开始分析,我们知道在 addObject 之前,会首先判断是否有当前的释放池,如果没有则创建,如果有,则直接使用,可想而知,在任何使用,任何情况,通过 addObject 只需要创建一个释放池便已经足够使用了。事实上也是如此。再来看 pop 方法。
    return;
      
      
  • }
  • intnCount=m_pReleasePoolStack->count();
  • //清楚对创建对象的引用
  • m_pCurReleasePool->clear();
  • //如果大于1,这也保证着,在任何时候,总有一个释放池是可以使用的
  • if(nCount>1)
  • {
  • //移除当前的释放池
  • m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
  • //}
  • //将当前释放池设定为前一个释放池,也就是“出栈”的操作
  • m_pCurReleasePool=(CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount-2);
  • }
  • /*m_pCurReleasePool=NULL;*/
  • }
  • 看到这里 我就不解了!什么情况下才能用到多个释放池?按照设计的逻辑根本用不到。带着这个疑问,我在 CCPoolManager::push() 方法之内添加了一句话打印(修改源代码) CCLog("这里要长长长的 **********"); ,然后重新编译源文件,运行程序,发现实际的使用中,push 只被调用了两次!我们知道,通过 addObject 可能会自动调用 push() 一次,但也仅有一次,所以一定是哪里手动调用了 push() 方法,才会出现这种情况,所以我继续翻看源代码,定位到了 bool CCDirector::init(void) 方法,在这里进行了游戏的全局初始化相关工作:
      
      
  • boolCCDirector::init(void)
  • {
  • CCLOG("cocos2d:%s",cocos2dVersion());
  • ...
  • ...
  • m_dOldAnimationInterval=m_dAnimationInterval=1.0/kDefaultFPS;
  • m_pobScenesStack=newCCArray();
  • m_pobScenesStack->init();
  • ...
  • ...
  • m_fContentScaleFactor=1.0f;
  • ...
  • ...
  • //touchDispatcher
  • m_pTouchDispatcher=newCCTouchDispatcher();
  • m_pTouchDispatcher->init();
  • //KeypadDispatcher
  • m_pKeypadDispatcher=newCCKeypadDispatcher();
  • //Accelerometer
  • m_pAccelerometer=newCCAccelerometer();
  • //这里手动调用了push方法,而在这之前的初始化过程中,间接的使用了CCObject的autorelease,已经触发过一次push方法
  • CCPoolManager::sharedPoolManager()->push();
  • true;
  • }
  • 所以我们便能够看到 push 方法被调用了两次,但其实如果我们把这里的手动调用放在方法的开始处,或者干脆就不使用 CCPoolManager::sharedPoolManager()->push(); ,对程序也没任何影响,这样从头到尾,只创建了一个自动释放池,而这里多创建的一个并没有多大的用处。 或者用处不甚明显,因为多创建一个释放池是有其效果的,效果具体体现在哪里,那就是 可以使调用 push() 方法之前的对象,多存活一帧。,因为 pop 方法只对当前释放池做了 clear 释放。为了方便起见,我们使用 Cocos2d-x 内存管理浅说 里面的方法观察每一帧的情况,看下面测试代码:
    //关键代码如下
  • CCLog("updateindex:%d",updateCount);
  • //在不同的帧做相关操作,以便观察
  • if(updateCount==1){
  • //创建一个自动管理对象
  • layer=LSLayer::create();
  • //创建一个新的自动释放池
  • CCPoolManager::sharedPoolManager()->push();
  • //再创建一个自动管理对象
  • sprite=LSSprite::create();
  • }elseif(updateCount==2){
  • }if(updateCount==3){
  • }
  • CCLog("updateindex:%dend",0); background-color:inherit">///打印代码如下
  • cocos2d-xdebuginfo[updateindex:1]
  • //第一帧创建了两个自动管理对象
  • cocos2d-xdebuginfo[LSLayer().()]
  • cocos2d-xdebuginfo[LSSprite().()]
  • cocos2d-xdebuginfo[updateindex:1end]
  • //第一个过度帧只释放了sprite对象
  • cocos2d-xdebuginfo[LSSprite().~()]
  • cocos2d-xdebuginfo[updateindex:2]
  • cocos2d-xdebuginfo[updateindex:2end]
  • //第二个过度帧释放了layer对象
  • cocos2d-xdebuginfo[LSLayer().~()]
  • cocos2d-xdebuginfo[updateindex:3]
  • cocos2d-xdebuginfo[updateindex:3end]
  • 可以对比 sprite 和 layer 对象,两个对象被放在了不同的自动释放池之中。这就是 手动调用 push() 方法所能达到的效果,至于怎么利用这个特性,帮助我们完成特殊的功能?我想还是不用了,这会增加我们程序设计的 复杂度,在我看来,甚至想把,cocos2d-x 2.0.4 中那唯一一次调用的 push() 给删了,以保持简单(程序的第一次初始化“可能”会用到这个特性,不过目测是没有多大关系的了 : P),在这里只系统通过这个例子理解 自动释放池是怎样被管理的即可!
    从自动释放池管理 创建时期 对象,再到对释放池的管理,我们已经大概了解了一个对象的生命周期经历了哪些! 下面简单说说 使用时期 的对象管理。
    树形结构的链式反应
    文中我们知道了,自动释放池的存在意义,在于对象 创建时期 的处理,而仅仅理解了自动释放池,对于我们使用 cocos2d-x 不够,远远不够!自动释放池只是解决对象初始化的问题,仅此而已,而要在整个使用过程中,相对的自动化管理,那么必须理解两个概念,树形结构 和 链式反应 (链式反应,不错的说法,就像原子弹爆炸一样,一传十,十传百 :P)
    我们当前运行这一个场景,场景初始化,添加了很多层,层里面有其它的层或者精灵,而这些都是 CCNode 节点,以场景为根,形成一个树形结构,场景初始化之后(一帧之后),这些节点将完全 依附 (内部通过 retain) 在这个树形结构之上,全权交由树来管理,当我们 砍去一个树枝,或者将树 连根拔起,那么在它之上的“子节点”也会跟着去除(内部通过 release),这便是链式反应。
    Cocos2d-x 内存管理的一种实现,此文这种实现的本质既是 强化这种 链式反应,也是解决内存可能出错的一个解决方案。如下(前文片段,具体详见前文):
    //方式一:那么我们的使用过程
      
      
  • LUser*lu=LUser::create();
  • lu->m_sSprite=CCSprite::create("a.png");
  • //如果这里不retain则以后就用不到了
  • lu->m_sSprite->retain();
  • //方式二:使用方法
  • LUser*lu=LUser::create();
  • lu->m_sUserName="一叶";
  • //这里的sprite会随着lu的消亡而消亡,不用管释放问题了
  • lu->setSprite(CCSprite::create("a.png"));
  • 我们看到方式二相比方式一的设计,它通过 setSprite 内部对 sprite 本身 retain,从而实现链式反应,而不是直接使用 lu->m_sSprite->retain();,这样的好处是,我只要想着释放 LUser,而不用考虑LUser 内部 sprite 的引用情况就行了。如此才能把 cocos2d-x 内存的自动管理特性完全发挥 ~
    而要实现这样管理的一个明显特征就是,隐藏 retain 和 release 操作 ~
    稍作总结
    关于 cocos2d-x 的内存管理从使用到原理,系列文章就到这里了!(三篇也算系列 = =!) 由表象到内部的思考探索过程,其实在 浅说 当中对 cocos2d-x 的使用,便已经能够知晓内部细节设计之一二,透过现象看本质!三篇文章包含了,使用浅说(简单的测试),一种防止内存泄漏的设计(加强链式反应),最后纵览 cocos2d-x 的内存管理框架,对 CCObject 的生命周期做了简单的说明,当然其中还是隐藏一些细节的,比如管理都是用 CCArray 来管理,但我们并没有对 CCArray 做介绍,它是如何添加元素,如何引用等。在任何时候我们只针对一个问题进行思考,那我们该把 CCArray 这样的辅助工具类放在何处,如果你了解当然最好,不过不了解,那便 存疑 ,然后对相应的问题,分而治之 ~

    版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

    相关推荐


        本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
    Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
    第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
        Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
    1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
       Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
     下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
    分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
    1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
    原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
    2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
    原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
    环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
    1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
    原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
    原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
    为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
    Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
    需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
    v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是