前一篇我们讲到cocos2d-x引擎中的自动内存管理机制,一个被自动管理的对象从new出来之后到被放到autoreleasepool。那么接下来,对象是如何被引擎自动delete掉的呢?下面这篇文章将介绍一下。
首先我们要知道,cocos2d-x的引擎线程是单线程的,它不停的调用一个主循环来绘制当前的Scene ,同时对一些自动释放的对象进行管理。
一、下面我就从一个cocos2dx的ios平台程序中进入到这个主循环:
①首先定位到 :ios文件夹中的 AppController.mm文件中
在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法的最后有这样一行代码:cocos2d::CCApplication::sharedApplication()->run();
②接着进入run()这个方法
- int CCApplication::run()
- {
- if (applicationDidFinishLaunching())
- {
- [[CCDirectorCaller sharedDirectorCaller] startMainLoop];
- }
- return 0;
- }
- -(void) startMainLoop
- {
- // CCDirector::setAnimationInterval() is called, we should invalidate it first
- [displayLink invalidate];
- displayLink = nil;
- displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(doCaller:)];
- [displayLink setFrameInterval: self.interval];
- [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- }
- -(void) doCaller: (id) sender
- {
- cocos2d::CCDirector::sharedDirector()->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。
- 在帧过渡的时候,游戏主循环中的 CCPoolManager::sharedPoolManager()->pop(); 方法调用一次,会自动解除“自身引用”,其实也就是将自动释放池中的对象统统release一次。所以该对象至少可以活一帧。(当然,如果当对象的引用计数为0时,就会释放对象。)
- 当然,为了保持对象不被释放掉,那么必须有管理对象(其实也是一个对象)来“使用”该对象,也即管理对象对该对象保持引用,这样管理对象就会对该对象进行retain的操作,使其的引用计数不会为0。
- 所以说解除自动释放池对对象的自身引用(pop),目的就是要让出引用权利,给实际使用这个对象的管理对象。(这里也可以解释我们在编程过程中可能会遇到的一个问题:create一个变量之后,我们在使用的时候突然出现内存错误,显然就是缺乏管理对象的对其引用,解决的方法也很简单,要使用这个对象的管理对象,对其retain一下就ok了)
三、自动释放池解除引用的过程(pop)
首先,我们进入pop()这个方法中:
- 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中的内存管理,其实总结来说的话就两个方面的内容:
①把对象添加到自动释放池进行自动管理(创建时期);
②自动释放池解除对对象的自动管理,交由使用该对象的对象进行管理(使用时期)。
把握理解其中的过程就应该没有什么问题的!