cocos2dx基于引用计数管理内存,所有继承自CCObject的对象都将获得引用计数的能力,可通过调用retain成员函数用于引用计数值,调用release减少引用计数值,当计数值减为0时销毁对象.

cocos2dx的对象管理是树形结构的,可通过调用父亲节点的addChild成员函数将一个子节点对象添加到父节点中,当子节点被添加到父亲节点中,子节点的引用计数值加1,如果通过removeChild将子节点从父节点中移除子节点的引用计数值减1.当父节点被销毁时,会遍历其所有的子节点,将其子节点的引用计数值减1,当子节点的计数值为0,销毁子节点.

提到cocos2dx的内存管理,就不得不提autorelease.

我们随便查看一个函数:

CCSprite* CCSprite::createWithTexture(CCTexture2D *pTexture)
{
CCSprite *pobSprite = new CCSprite();
if (pobSprite && pobSprite->initWithTexture(pTexture))
{
pobSprite->autorelease();
return pobSprite;
}
CC_SAFE_DELETE(pobSprite);
return NULL;
}

  

注意到CCSprite被生成并成功初始化之后(initWithTexture)立刻就调用了autorelease函数.那么autorelease的作用到底是什么.首先当一个对象A被创建出来之后它的引用计数值必定为1,如果我们将A添加到一个父节点B中,此时它的引用计数值为2.之后销毁父节点B,依据上面的描述,父节点被销毁时会将其子节点的引用计数减1.也就是说父节点B被销毁之后A的引用计数值为1。那么我们就必须手动在不再需要A的时候调用releasedelete来将A销毁.为了减轻程序员的负担,cocos2dx提供了CCAutoreleasePool当节点A被添加到autoreleasepool之后,我们就不在需要关注A的销毁了.因为引擎会在每帧结束之后清理autoreleasepool中的对象,调用它的release如果如果对象引用计数值降为0将对象销毁.

下面是CCAutoreleasePool::addObject,autorelease最终调用的就是这个函数:

void CCAutoreleasePool::addObject(CCObject* pObject)
{
m_pManagedObjectArray->addObject(pObject); CCAssert(pObject->m_uReference > , "reference count should be greater than 1");
++(pObject->m_uAutoReleaseCount);
pObject->release(); // no ref count, in this case autorelease pool added.
}

CCAutoreleasePool中添加一个对象,底层管理容器是一个array对象被添加到array的时候不检测重复性,所以个一对象可以被添加多次.添加完成后需要调用release函数减少引用计数,因为在array->addObject中调用了retain.

应当注意避免对一个object多次调用autorelease,对object调用一次autorelease将导致当前帧结束时对这个对象调用一次release,假设一个对象没被添加进任何容器,则其引用计数值为1,如果调用两次autorelease则当前帧结束时会调用两次release其中第二次必定是在一个已经被销毁的对象上执行的.而如果这一对象已经添加另外一个容器,则会导致那个容器在当前帧结束之后持有一个已经被销毁的对象指针.此问题在cocos2dx rc3中已经添加了assert处理,对象销毁时判断在pool是否有另外的实例存在,如果有则报错.虽然只在debug版本中处理,但也不用通过遍历vector这么低效的手段来判断重复实例的存在吧.难道2.2.3版本的m_uAutoReleaseCount就没有给开发人员提供一点启发?

我们来看看引擎的主循环:

void CCDisplayLinkDirector::mainLoop(void)
{
if (m_bPurgeDirecotorInNextLoop)
{
m_bPurgeDirecotorInNextLoop = false;
purgeDirector();
}
else if (! m_bInvalid)
{
drawScene(); // release the objects
CCPoolManager::sharedPoolManager()->pop();
}
}

在每帧中调用drawScene(),之后就调用CCPoolManager::sharedPoolManager()->pop().

void CCPoolManager::pop()
{
if (! m_pCurReleasePool)
{
return;
} int nCount = m_pReleasePoolStack->count(); m_pCurReleasePool->clear(); if(nCount > )
{
m_pReleasePoolStack->removeObjectAtIndex(nCount-); // if(nCount > 1)
// {
// m_pCurReleasePool = m_pReleasePoolStack->objectAtIndex(nCount - 2);
// return;
// }
m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount - );
} /*m_pCurReleasePool = NULL;*/
}

好了关键函数就是m_pCurReleasePool->clear(),这个函数完成了对象的清理过程.

在这里我们发现了一点奇怪的地方,为啥有个m_pReleasePoolStack还有个m_pCurReleasePool.

首先来看下ReleasePool对象是在哪里被创建的:

void CCPoolManager::push()
{
//if(!m_pCurReleasePool){
CCAutoreleasePool* pPool = new CCAutoreleasePool(); //ref = 1
m_pCurReleasePool = pPool; m_pReleasePoolStack->addObject(pPool); //ref = 2 pPool->release(); //ref = 1
//}
}

然后我们跟踪程序的运行将断点放在push函数里,断点的第一次命中调用栈如下:

 libcocos2d.dll!cocos2d::CCPoolManager::push() 行  C++
libcocos2d.dll!cocos2d::CCPoolManager::getCurReleasePool() 行 C++
libcocos2d.dll!cocos2d::CCPoolManager::addObject(cocos2d::CCObject * pObject=0x003e9938) 行 C++
libcocos2d.dll!cocos2d::CCObject::autorelease() 行 C++
libcocos2d.dll!cocos2d::CCDictionary::create() 行 C++
libcocos2d.dll!cocos2d::CCConfiguration::init() 行 C++
libcocos2d.dll!cocos2d::CCConfiguration::sharedConfiguration() 行 C++
libcocos2d.dll!cocos2d::CCDirector::setDefaultValues() 行 C++
libcocos2d.dll!cocos2d::CCDirector::init() 行 C++
libcocos2d.dll!cocos2d::CCDirector::sharedDirector() 行 C++
IslandFight.exe!AppDelegate::applicationWillEnterForeground() 行 C++
libcocos2d.dll!cocos2d::CCEGLView::WindowProc(unsigned int message=, unsigned int wParam=, long lParam=) 行 C++
libcocos2d.dll!cocos2d::_WindowProc(HWND__ * hWnd=0x002a06aa, unsigned int uMsg=, unsigned int wParam=, long lParam=) 行 C++

第二次调用栈:

libcocos2d.dll!cocos2d::CCPoolManager::push() 行  C++
libcocos2d.dll!cocos2d::CCDirector::init() 行 C++
libcocos2d.dll!cocos2d::CCDirector::sharedDirector() 行 C++
IslandFight.exe!AppDelegate::applicationWillEnterForeground() 行 C++
libcocos2d.dll!cocos2d::CCEGLView::WindowProc(unsigned int message=, unsigned int wParam=, long lParam=) 行 C++
libcocos2d.dll!cocos2d::_WindowProc(HWND__ * hWnd=0x002a06aa, unsigned int uMsg=, unsigned int wParam=, long lParam=) 行 C++

也就是说总共创建了两个ReleasePool,ReleasePool都被添加到m_pReleasePoolStack成员中,这个成员是一个arraym_pCurReleasePool则指向m_pReleasePoolStack的最后一个成员.

接着我们将断点放到CCPoolManager::pop()中,可以发现第一次pop命中的时候nCount == 2,所以执行if(nCount > 1)之后的流程, 这个流程的处理就是将m_pReleasePoolStack的最后一个成员销毁,然后将m_pCurReleasePool指向m_pReleasePoolStack中最后一个成员.

继续观察程序的运行可以发现,在第一帧之后m_pReleasePoolStack中实际上永远只剩一个成员.我将

bool CCDirector::init(void)中的CCPoolManager::sharedPoolManager()->push()注释掉,程序依旧能正确的运行.

我搜索cocos2dx 2.2.3和cocos2dx 3.0rc源代码,并无找到必须启用多个ReleasePool的地方, 那么多ReleasePool存在的意义就仅剩调试了.

研究cocos2dx不久,文中有理解不当的地方欢迎指正.


cocos2dx内存管理的更多相关文章

  1. cocos2d-x内存管理

    Cocos2d-x内存管理 老师让我给班上同学讲讲cocos2d-x的内存管理,时间也不多,于是看了看源码,写了个提纲和大概思想 一.   为什么需要内存管理 1. new和delete 2. 堆上申 ...

  2. cocos2dx内存管理的一些看法

    今年年初进入一家游戏公司,正式开始游戏引擎的学习,之前的ios学习,对现在的游戏引擎学习还是有很大的帮助的,虽然使用c++,但却能时刻感受到ios框架对于cocos2dx的巨大影响. 由于之前一直使用 ...

  3. 2、COCOS2D-X内存管理机制

    在C++中.动态内存分配是一把双刃剑,一方面,直接訪问内存地址提高了应用程序的性能,与使用内存的灵活性.还有一方面.因为程序没有正确地分配与释放造成的比如野指针,反复释放,内存泄漏等问题又严重影响着应 ...

  4. cocos2dx内存管理机制

    参考以下两篇文章 http://blog.csdn.net/ring0hx/article/details/7946397 http://blog.csdn.net/whuancai/article/ ...

  5. cocos2d-x内存管理(见解)

    cocos2d-x 延续了cocos2d 和OC的引用计数的内存管理机制! 下面我们来看看CCDriectro类 CCPoolManager::sharedPoolManager()->push ...

  6. cocos2d-x 内存管理浅析

    Cocos2d-x用create创建对象, 这个方法已经被引擎封装成一个宏定义了:CREATE_FUNC, 下面是这个宏定义的实现: #define CREATE_FUNC(__TYPE__) \   ...

  7. cocos2dx 内存管理的理解

    关于引擎内存管理的细节,网上有大量的详解,这里概括一下: cocos2d-x 的世界是基于 CCObject 类构建的,所以内存管理的本质就是管理一个个 CCObject. //CCObject 内部 ...

  8. cocos2dx 内存管理

    转载自 ocos2dx 内存管理 - 小花原创博客 - 博客频道 - CSDN.NET http://blog.csdn.net/ring0hx/article/details/7946397 coc ...

  9. Cocos2d-x内存管理研究<二>

    http://hi.baidu.com/tzkt623/item/46a26805adf7e938a3332a04   上一篇我们讲了内核是如何将指针加入管理类进行管理.这次我将分析一下内核是如何自动 ...

  10. Cocos2d-X内存管理研究<一>

    http://hi.baidu.com/tzkt623/item/651ca7d7a0aff6e055347f67        半夜没事干,研究内核,作为我cocos2d-x的第一篇教程.cocos ...

随机推荐

  1. EF6 简单增删改查示例代码

    示例一: private DbContext _dbContext; public DbContext CurrentContext { get { if (_dbContext == null) { ...

  2. 让.Net程序支持命令行启动

    很多时候,我们需要让程序支持命令行启动,这个时候则需要一个命令行解析器,由于.Net BCL并没有内置命令行解析库,因此需要我们自己实现一个.对于简单的参数来说,自己写一个字符串比较函数来分析args ...

  3. Android:Unable to find explicit activity class

    写了两个Activity,确定java代码和xml配置文件没问题之后,运行工程,报错: E/AndroidRuntime(10513): FATAL EXCEPTION: main E/Android ...

  4. [转载]安装Oracle11gR2先决条件检查失败的详细解决处理过程

    原文地址:安装Oracle11gR2先决条件检查失败的详细解决处理过程作者:四海名汀 最近在32位Win7系统下安装Oracle11g发现一系列错误,现将详细的错误解决过程记录如下,以供大家参考. 一 ...

  5. 如何实现一个Java Class 解析器

    原文出处: tinylcy 最近在写一个私人项目,名字叫做ClassAnalyzer,ClassAnalyzer的目的是能让我们对Java Class文件的设计与结构能够有一个深入的理解.主体框架与基 ...

  6. Apache log4net™ 手册——介绍【翻译】

    原文地址 本文内容 配置 配置属性 应用程序 appSettings 配置文件 配置语法 追加器(Appenders) 筛选器(Filters) 布局(Layouts) 根记录器(Root Logge ...

  7. eclipse core expression usage

    http://codeandme.blogspot.com/2012/04/expression-examples.html We need to set checkEnabled on the vi ...

  8. sublime text3怎么安装Package Control

    sublime text3地址:https://packagecontrol.io/installation#st3 1.打开Preferences——Browse Packages,打开一个文件夹C ...

  9. gitlab Docker容器创建命令以及从容器中备份gitlab仓库示例

    Gitlab容器启动命令: docker run -d --name gitlab --publish : --publish : --hostname gitlab-server --volume ...

  10. phpBB3.2 自动检测浏览器语言

    这是根据HTTP request header里的Accept-Language信息来处理的. 首先看一下Accept-Language的格式 Accept-Language: <languag ...