由于cocos2dx我们的使用c++写的,所以内存管理就是一个绕不过去的坎,这个你不懂内存只懂业务逻辑的话,还玩什么c++,今天看了半天这个东西,其实本质上是理解的,但是就是有一个过不去的坎,终于在今天晚上搞定了,于是想给大家分享一下。争取我把网上的优质的精华在经过自己的理解,分享给大家啊。
内存的管理我们一般有两种方式,引用计数和垃圾回收。我们cocos2dx采用的就是引用计数,而很火的java就是垃圾回收。引用计数,垃圾回收详解:
引用计数:通过给每个对象维护一个引用计数器,记录该对象当前被引用的次数。当对象增加一次引用时,计数器加一:而失去一次引用时,计数器减一;当计数为0时,标志着该对象的生命周期结束,自动触发对象的回收释放。引用计数解决了对象的生命周期管理问题,但堆碎片化的和管理繁琐的问题仍然存在。
垃圾回收:他通过引入一种自动的内存回收期,试图将程序员从复杂的内存管理任务中完全解放出来。他会自动跟踪每一个个对象的所有引用,以便找到正在使用的对象,然后是房企与不再需要的对象。垃圾回收期通常是作为一个单独的低级别的线程运行的,在不可预知的情况下对内存堆中已经死亡的或者长时间没有使用过的对象进行清除和回收 。
我们着重的说关于引用计数,他的原理就是我们引用一个对象,就让对象的引用计数加一,失去一个引用一个对象就让引用计数减一,在cocos2dx中的方法就是retain和release,我们看CCObject,在CCObject里面有一个属性m_uReference就是引用计数的。
CCObject::CCObject(void)<span style="white-space:pre"> </span>
: m_nLuaID(0)
, m_uReference(1) // when the object is created, the reference count of it is 1注意这里,默认这个m_uReference是1的
, m_uAutoReleaseCount(0)
{
static unsigned int uObjectCount = 0;
m_uID = ++uObjectCount;
}
void CCObject::retain(void)
{
CCAssert(m_uReference > 0, "reference count should greater than 0");
++m_uReference;
}
retain方法,每次将m_uReference加一
void CCObject::release(void)
{
CCAssert(m_uReference > 0, "reference count should greater than 0");
--m_uReference;
if (m_uReference == 0)
{
delete this;
}
}
release每次将m_uReference减一,并且如果为0的话就delete掉。
如果我们手动管理,利用上面的方法就可以了。
但是我们主要说自动管理,自动管理其实就是你只管用就行了,关于释放的问题我们统一交给Cocos2dx引擎来释放,无需我们手动调用release
一般我们的类中会有一个create方法,这个方法大概是这样子的
MyScene * MyScene::create()
{
MyScene *pRet = new MyScene;
if (pRet && pRet->init())
{
pRet->autorelease();<span style="white-space:pre"> </span>//注意这里,在这个时候我们就将这个对象交给了引擎了,我们就无需再次手动释放,等待引擎自己释放就行了
return pRet;
}
else
{
CC_SAFE_DELETE(pRet);
}
return NULL;
}
有些人会问了,你简简单单的因为这么一句话就不释放了,就交给引擎了,你是怎么实现的,这玩意儿靠谱么,别怕,我们进入方法看看
CCObject* CCObject::autorelease(void)
{
CCPoolManager::sharedPoolManager()->addObject(this); //我们再次进去,见下面方法
return this;
}
void CCPoolManager::addObject(CCObject* pObject)
{
getCurReleasePool()->addObject(pObject); //我们再次进去,见下面方法
}
void CCAutoreleasePool::addObject(CCObject* pObject)
{
m_pManagedObjectArray->addObject(pObject); //这里如果我们继续往下走,那么我们终究会找到一个pObject.retain的方法的,
CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");
++(pObject->m_uAutoReleaseCount);
pObject->release(); // no ref count, in this case autorelease pool added.
}
首先我们要说明CCAutoreleasePool叫做自动释放池,在CCPoolManager
(自动释放池管理类)类里面我们有个成员变量 CCArray * m_pReleasePoolStack;这个是自动释放池栈,里面存放CCAutoreleasePool的实例。
CCAutoreleasePool内部有一个CCArray * m_pManagedObjectArray,这个是他内部的一个对象数组。
大体上他们的关系就是如此,我们每次自动托管对象以后,就会加到这个内存释放池里面,你可能会问了,这玩意儿我们不释放,那什么时候释放呢,答案就是每一次帧循环就释放一次,并且重新创建一个自动释放池。
我们看mainLoop代码
void CCPoolManager::pop()
{
if (! m_pCurReleasePool)
{
return;
}
int nCount = m_pReleasePoolStack->count();
m_pCurReleasePool->clear(); //大家注意看这里了,这个意思不就是内存池清空吗,我们倒是要看看他怎么清空
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;*/
}
void CCAutoreleasePool::clear()
{
if(m_pManagedObjectArray->count() > 0)
{
//CCAutoreleasePool* pReleasePool;
#ifdef _DEBUG
int nIndex = m_pManagedObjectArray->count() - 1;
#endif
CCObject* pObj = NULL;
CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)
{
if(!pObj)
break;
--(pObj->m_uAutoReleaseCount);//这里的意思就是自动管理的标志变为0了
//(*it)->release();
//delete (*it);
#ifdef _DEBUG
nIndex--;
#endif
}
m_pManagedObjectArray->removeAllObjects();
}
}
大概就是这个样子的,我们想象如果我们只是简简单单的create了一个经理,并没有把它挂到渲染树上面,我们的引用计数肯定是1啊,然后再经过自动释放池的减减,就会被释放了啊。
第一种情况:
CCScene *pscene = CCScene::create(); //引用计数为1,在内部默认autorelease了
。。。经过了帧循环的清栈了,引用减一,pscene就被干掉了。
第二种情况:
CCScene *pscene = CCScene::create(); //引用计数为1,在内部默认autorelease了
addChild(pscene);//引用计数为2,
。。。经过了帧循环的清栈,引用减一,引用计数就变为1,并且下次就不会再这个自动释放池里了,所以这个精灵就可以一直在渲染树上了,我们什么时候想删他,后续要想释放这个“精灵”,我们还是需要手工调用release,或再调用其autorelease方法。
我小做总结一下,这个嘛就是,我们吧一个CCObject执行了autorelease方法,自动释放池就会默认在下一帧循环开始的时候给我们-1,因为之前的我们托管了,理论上,如果引用计数减一之后为零了,就是本身应该我们释放的,但是我们托管给了引擎,引擎就会义不容辞的帮我们把它释放掉。
如果我们不仅自己创建了,还把它加到了渲染树上,表示这个精灵我们要继续用,自动释放池就会在将引用计数减一后为一,引擎就会知道你在creat这个精灵之后你还在用,我就不管了,让他继续活着,我还是要清理自动释放池,因为我要为这次的帧循环做准备。
不知不觉都写到这个点了,本来还是想再说一点,早点刷牙睡觉吧,今天这个真的是搞得我天昏地暗,日月无光,道理我懂,就是没有把思想转换过来,开始没有弄懂为什么就这么释放了,后来知道了,这个本来应该我们干,但是有时候这些是注册函数,中断函数等等,我们不知道什么时候干, 所以就要交给引擎来干,因为他知道怎么干。
刷牙,睡觉,各位晚安吧。。。。。。。。。