转自:http://www.2cto.com/kf/201403/288679.html
首先我们要知道,cocos2d-x的引擎线程是单线程的,它不停的调用一个主循环来绘制当前的Scene ,同时对一些自动释放的对象进行管理。
一、现在开始定位,我们知道win32 下cocos2dx程序入口
CCApplication::sharedApplication()->run();
run方法里面有这么一句
CCDirector::sharedDirector()->mainLoop();
进入mainLoop方法查看
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(); 释放自动释放池中的对象。
二、在对pop释放对象分析的之前,首先有必要了解为什么需要自动释放池这个东西。
下面这段解释摘抄自:http://blog.leafsoar.com/archives/2013/06-04.html
我们知道 cocos2d-x 使用了自动释放池,自动管理对象,知其然!其所以然呢?为什么需要自动释放池,它在整个框架之中又起着什么样的作用!在了解这一点之前,我们需要 知道 CCObject 从创建之初,到最终销毁,经历了哪些过程。在此,一叶总结以下几点(重点理解):
- 刚创建的对象,而 为了保证在使用之前不会释放(至少让它存活一帧),所以自引用(也就是初始为1)
- 为了确定是否 实际使用,所以需要在一个合适的时机,解除自身引用。
- 而这个何时的时机正是在帧过度之时。
- 帧过度之后的对象,用则用矣,不用则弃!
- 由于已经解除了自身引用,所以它的引用被使用者管理(一般而言,内部组成树形结构的链式反应,如 CCNode)。
- 链式反应,也就是,如果释放一个对象,也会释放它所引用的对象。
上面是一个对象的大致流程,我们将对象分为两个时期,一个是刚创建时期,自引用为 1(如果为 0 就会释放对象,这是基本原则,所以要大于 0) 的时期,另一个是使用时期。上面说到,为了保证创建时期的对象不被销毁,所以自引用(并没有实际的使用)初始化为 1,这就意味着我们需要一个合适的时机,来解除这样的自引用。
何时?在帧过度之时!(这样可保证当前帧能正确使用对象而没有被销毁。)怎么样释放?由于是自引用,我们并不能通过其它方式访问到它,所以就有了自动释放池,我们 变相的将“自引用”转化“自动释放池引用”,来标记一个 “创建时期的对象”。
然后在帧过度之时,通过自动释放池管理,统一释放 “释放池引用”,也就意味着,去除了“自身引用”。帧过度之后的对象,才是真正的被使用者所管理。
总结:通过对上面这段话的理解我们就可以知道了:
-
一个对象在刚创建(create,即new+autorelease)的时候,是自动释放池保持对该对象的引用(“自身引用”),使得其引用计数为1。
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;*/ }
在这个方法中我们可以知道,其释放过程是这样的:在前面一篇文章中 Cocos2d-x 内存管理机制(1) 我们已经知道 CCPoolManager 对象自动释放管理类中保存着一个当前的对象自动释放池和一个对象释放池数组。
在pop方法中,首先是对当前的对象自动释放池进行clear操作,显然就是对当前释放池中的所有对象进行解除引用的操作。下面进入这个clear方法:
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); //(*it)->release(); //delete (*it); #ifdef _DEBUG nIndex--; #endif } m_pManagedObjectArray->removeAllObjects(); } }
我们在for循环中可以看到 --(pObj->m_uAutoReleaseCount); 这个是对引用对象解除自动对象管理,自动管理值减一。
对象添加到自动释放池的时候 m_uAutoReleaseCount = 1,表示给自动释放池自动管理;那么现在减1之后,m_uAutoReleaseCount = 0,表示不是给自动释放池管理了。
而且我们还可以注意到 CCARRAY_FOREACH_REVERSE 其解除引用是逆序的,由此我们也可以猜测到 :自动释放池采用的是一个栈结构(FILO)。
--m_uAutoReleaseCount 之后。就是 m_pManagedObjectArray->removeAllObjects(); 将自动释放池中的所有对象进行remove,那么我们跟踪这个remove的过程会发现,其实最终也是对对象进行release操作,因为对象autorelease之后,m_uReference 的值为1,那么这里release之后,就变成0了,那么就会自动释放这个对象。
所以经过这个clear之后,当前自动管理对象池中的所有对象都被解除了自身引用。
接着我们再回到pop方法中,
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); }
我们将当前的对象自动释放池clear之后,需要从自动释放池数组中删除该对象自动管理池;然后将数组中的倒数第二个自动释放池作为当前自动释放池。从这里,我们也可以大致理解到,自动释放池数组,其实本质上也是一个栈结构了吧,只不过是通过数组来实现的。
自动释放池解除引用的过程大致就是这样子了!
四、总结
这两篇文章大致讲述了cocos2dx中的内存管理,其实总结来说的话就两个方面的内容:
②自动释放池解除对对象的自动管理,交由使用该对象的对象进行管理(使用时期)。
把握理解其中的过程就应该没有什么问题的!