CCObject的分析:release retain基于2.2.3 增加3.2ref对比

CCSprite * fish = new CCSprite;
02. CCLOG("After new: %d",fish->retainCount());
03. fish->init();
04. CCLOG("After init: %d",fish->retainCount());
05. fish->retain();
06. CCLOG("After retain: %d",fish->retainCount());
07. fish->release();
08. CCLOG("After release: %d",fish->retainCount());
09. fish->autorelease();
10. CCLOG("After autorelease: %d",fish->retainCount()); //实际操作了+1-1.

结果:

After new: 1
After init: 1
After retain: 2
After release: 1
After autorelease: 1
“HelloCpp.exe”(Win32): 已加载“C:WINDOWSSysWOW64SogouTSF.ime”。无法查找或打开 PDB 文件。

 

01. class CC_DLL CCCopying
02. {
03. public:
04. virtual CCObject* copyWithZone(CCZone* pZone);
05. };
06.  
07. /**
08. * @js NA
09. */
10. class CC_DLL CCObject : public CCCopying
11. {
12. public:
13. // object id, CCScriptSupport need public m_uID
14. unsigned int        m_uID;
15. // Lua reference id
16. int                 m_nLuaID;
17. protected:
18. // count of references
19. unsigned int        m_uReference;
20. // count of autorelease
21. unsigned int        m_uAutoReleaseCount;
22. public:
23. CCObject(void);
24. /**
25. *  @lua NA
26. */
27. virtual ~CCObject(void);
28.  
29. void release(void);
30. void retain(void);
31. CCObject* autorelease(void);
32. CCObject* copy(void);
33. bool isSingleReference(voidconst;
34. unsigned int retainCount(voidconst;
35. virtual bool isEqual(const CCObject* pObject);
36.  
37. virtual void acceptVisitor(CCDataVisitor &visitor);
38.  
39. virtual void update(float dt) {CC_UNUSED_PARAM(dt);};
40.  
41. friend class CCAutoreleasePool;
42. };

如下:默认初始化reference为1,

 

 

01. CCObject::CCObject(void)
02. : m_nLuaID(0)
03. , m_uReference(1// when the object is created, the reference count of it is 1
04. , m_uAutoReleaseCount(0//
05. {
06. static unsigned int uObjectCount = 0;
07.  
08. m_uID = ++uObjectCount;
09. }

看看autorelease的源码就知道实现了+1和-1的操作:

 

 

1. CCObject* CCObject::autorelease(void)
2. {
3. CCPoolManager::sharedPoolManager()->addObject(this);
4. return this;
5. }
1. //<span style="font-family: Arial, Helvetica, sans-serif;">CCAutoreleasePool.cpp</span>
1. void CCPoolManager::addObject(CCObject* pObject)
2. {
3. getCurReleasePool()->addObject(pObject);
4. }
01. void CCAutoreleasePool::addObject(CCObject* pObject)
02. {
03. m_pManagedObjectArray->addObject(pObject); //赫然使用了CCArray。实现了+1的,随着3.2使用C++11语法后使用vector去管理就没有这么纠结了。
04.  
05.  
06. CCAssert(pObject->m_uReference > 1"reference count should be greater than 1");
07. ++(pObject->m_uAutoReleaseCount); //同时在当前的pool中增加管理
08. pObject->release(); // no ref count, in this case autorelease pool added.   //必须-1,否则泄露。
09. }

 

每个实现单例的类都应该提供清除操作:如下所示,以purge开头。

 

1. void CCPoolManager::purgePoolManager()
2. {
3. CC_SAFE_DELETE(s_pPoolManager);
4. }


在2.2系列中,每个CCAutoreleasePool实际上就是一个CCArray,存储了一系列CCObject*,通过m_uAutoReleaseCount来计数。
01. void CCAutoreleasePool::clear()
02. {
03. if(m_pManagedObjectArray->count() > 0)
04. {
05. //CCAutoreleasePool* pReleasePool;
06. #ifdef _DEBUG
07. int nIndex = m_pManagedObjectArray->count() - 1;
08. #endif
09.  
10. CCObject* pObj = NULL;
11. CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)
12. {
13. if(!pObj)
14. break;
15.  
16. --(pObj->m_uAutoReleaseCount);
17. //(*it)->release();
18. //delete (*it);
19. #ifdef _DEBUG
20. nIndex--;
21. #endif
22. }
23.  
24. m_pManagedObjectArray->removeAllObjects();
25. }
26. }


 

回顾一下CCDiretor的初始化源码段:

 

01. // scheduler
02. m_pScheduler = new CCScheduler(); // 计时器
03. // action manager
04. m_pActionManager = new CCActionManager(); //动作管理器
05. m_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false);
06. // touchDispatcher
07. m_pTouchDispatcher = new CCTouchDispatcher();  //触摸信号管理器
08. m_pTouchDispatcher->init();
09.  
10. // KeypadDispatcher
11. m_pKeypadDispatcher = new CCKeypadDispatcher(); //键盘信号管理器
12.  
13. // Accelerometer
14. m_pAccelerometer = new CCAccelerometer(); // 加速器管理器
15.  
16. // create autorelease pool
17. CCPoolManager::sharedPoolManager()->push(); //创建一个当前的pool并加入PoolManager中,还是一个CCArray,名为:m_pReleasePoolStack,实际上触控更换为vector是从2.0的版本左右就计划好了。2.x版本是个过渡,也就是说,了解了2.x,更具版本更新说明,3.x不存在代沟。
通告层NotificationNode使用说明

 

下面有个NotificationNode,实际上和Scene属于UI种类,因为是后绘制,会遮盖当前scene,用途在于loading或者提示这种弹窗,记得触摸屏蔽的相关设置

01. // Draw the Scene
02. void CCDirector::drawScene(void)
03. {
04. // calculate "global" dt
05. calculateDeltaTime();
06.  
07. //tick before glClear: issue #533
08. if (! m_bPaused)
09. {
10. m_pScheduler->update(m_fDeltaTime);
11. }
12.  
13. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
14.  
15. /* to avoid flickr, nextScene MUST be here: after tick and before draw.
16. XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */
17. if (m_pNextScene)
18. {
19. setNextScene();
20. }
21.  
22. kmGLPushMatrix();
23.  
24. // draw the scene
25. if (m_pRunningScene)
26. {
27. m_pRunningScene->visit();
28. }
29.  
30. // draw the notifications node
31. if (m_pNotificationNode)
32. {
33. m_pNotificationNode->visit();
34. }
35.  
36. if (m_bDisplayStats)
37. {
38. showStats();
39. }
40.  
41. kmGLPopMatrix();
42.  
43. m_uTotalFrames++;
44.  
45. // swap buffers
46. if (m_pob<a href="http://www.it165.net/Pro/pkgame/" target="_blank" class="keylink">OpenGL</a>View)
47. {
48. m_pob<a href="http://www.it165.net/Pro/pkgame/" target="_blank" class="keylink">OpenGL</a>View->swapBuffers();
49. }
50.  
51. if (m_bDisplayStats)
52. {
53. calculateMPF();
54. }
55. }

原以为会是在drawscene内调用,谁知不是。这是个题外话,无关紧要的。

 

回归正题:

在经过CCDisplayLinkDirector继承CCDirector,在mainLoop中调用poolManager,这个才是真正的cocos程序循环,程序入口的是win32或者ios的主程序循环:

简化版:

 

01. int CCApplication::run()
02. {
03. <span style="white-space:pre">// Initialize instance and cocos2d.
04. if (!applicationDidFinishLaunching())
05. {
06. return 0;
07. }
08.  
09.  
10. CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
11. pMainWnd->centerWindow();
12. ShowWindow(pMainWnd->getHWnd(), SW_SHOW);
13.  
14.  
15. while (1)
16. {
17. if (! PeekMessage(&msg, NULL, 00, PM_REMOVE))
18. {
19. // Get current time tick.
20. QueryPerformanceCounter(&nNow);
21.  
22.  
23. // If it's the time to draw next frame, draw it, else sleep a while.
24. if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart)
25. {
26. nLast.QuadPart = nNow.QuadPart;
27. CCDirector::sharedDirector()->mainLoop();
28. }
29. else
30. {
31. Sleep(0);
32. }
33. continue;
34. }
35.  
36.  
37. if (WM_QUIT == msg.message)
38. {
39. // Quit message loop.
40. break;
41. }
42.  
43.  
44. // Deal with windows message.
45. if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
46. {
47. TranslateMessage(&msg);
48. DispatchMessage(&msg);
49. }
50. }</span>
51.  
52.  
53.  
54. }


 

 

01. void CCDisplayLinkDirector::mainLoop(void)
02. {
03. if (m_bPurgeDirecotorInNextLoop) /*虽然cocos2d允许多个director,准备兼容多窗口程序(实际上到目前3.2为止,cocos2d应用多为是单窗口,只有一个窗口句柄,支持多场景、层,支持C++11多线程的引擎 */
04. {//导演类清理自己
05. m_bPurgeDirecotorInNextLoop = false;
06. purgeDirector();
07. }
08. else if (! m_bInvalid)
09. {
10. drawScene();
11.  
12. // release the objects
13. CCPoolManager::sharedPoolManager()->pop();       
14. }
15. }


 

实际上终于回到了我们要密切关注的CCObject内存回收机制了:

每次pop()都去调用当前CCAutoreleasePool的clear操作,因为原理在于Director将游戏世界切片为每一帧,并为每一帧设定绘制时间长度,当GPU和CPU在设备上完成上一帧的制作,会计算一个时间长度,长度不够时会占用当前帧的可用时长,当前帧绘制的必要时长于剩余帧时长时跳过当前帧的CPU执行和GPU绘制,这就是为什么掉帧和碰撞穿透的原因,而在clear操作中,代码如下:

 

01. void CCAutoreleasePool::clear()
02. {
03. if(m_pManagedObjectArray->count() > 0)
04. {
05. //CCAutoreleasePool* pReleasePool;
06. #ifdef _DEBUG
07. int nIndex = m_pManagedObjectArray->count() - 1;
08. #endif
09.  
10. CCObject* pObj = NULL;
11. CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)
12. {
13. if(!pObj)
14. break;
15.  
16. --(pObj->m_uAutoReleaseCount);
17. //(*it)->release();
18. //delete (*it);
19. #ifdef _DEBUG
20. nIndex--;
21. #endif
22. }
23.  
24. m_pManagedObjectArray->removeAllObjects();
25. }
26. }

将当前帧的数据清理掉,m_uAutoReleaseCount变成0,但是仍未真正释放内存。这时候内存管理机制就依靠m_uReference来辨别是否需要清理。所有只使用addChild而不retain的都会随着父节点被清理掉。一方面我们可以自己pushPool,另一方面可以使用挂载在m_pobScenesStack中当前scene下。poolManager并不管理m_pobScenesStack主线的节点,只管理掉落在当前pool中通过autorelease增加至pool的CCObject。

 

3.2 ref对比待续

ref 内部变化:变得清爽多了,除了增加了 CC_USE_MEM_LEAK_DETECTION 预编译模式下的内存跟踪:(这两个不是ref内部函数)

static void trackRef(Ref* ref);
static void untrackRef(Ref* ref);

外,减少了原先CCObject中的各种引用,只剩下:_referenceCount,无论lua还是js、c++,公用一个引用计数,C++分支真的往库或者大型3D多线程发展啦。只要坚持了过渡期,学好python、Linux的相关编译,手游纯3D的可行性很高。

下面才是新增的内存追踪显示函数:

 

1. #if CC_USE_MEM_LEAK_DETECTION
2. public:
3. static void printLeaks();
4. #endif

新的AutoreleasePool已经使用:std::vector<Ref*> _managedObjectArray; 标准C++作为容器,通过ref.autorelease()调用的代码中不在进行+-1啦,

 

 

1. void AutoreleasePool::addObject(Ref* object)
2. {
3. _managedObjectArray.push_back(object);
4. }

同时在mainLopp中变成了直接调用Autorelease的clear()操作:

 

 

01. void DisplayLinkDirector::mainLoop()
02. {
03. if (_purgeDirectorInNextLoop)
04. {
05. _purgeDirectorInNextLoop = false;
06. purgeDirector();
07. }
08. else if (! _invalid)
09. {
10. drawScene();
11.  
12. // release the objects
13. PoolManager::getInstance()->getCurrentPool()->clear();
14. }
15. }


 

3.2中

01. void Director::drawScene()
02. {
03. // calculate "global" dt
04. calculateDeltaTime();
05.  
06. // skip one flame when _deltaTime equal to zero.
07. if(_deltaTime < FLT_EPSILON)
08. {
09. return;
10. }
11.  
12. if (_openGLView)
13. {
14. _openGLView->pollInputEvents();
15. }
16.  
17. //tick before glClear: issue #533
18. if (! _paused)
19. {
20. _scheduler->update(_deltaTime);
21. _eventDispatcher->dispatchEvent(_eventAfterUpdate);
22. }
23.  
24. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
25.  
26. /* to avoid flickr, nextScene MUST be here: after tick and before draw.
27. XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */
28. if (_nextScene)
29. {
30. setNextScene();
31. }
32.  
33. pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
34.  
35. // draw the scene
36. if (_runningScene)
37. {
38. _runningScene->visit(_renderer, Mat4::IDENTITY, false);
39. _eventDispatcher->dispatchEvent(_eventAfterVisit);
40. }
41.  
42. // draw the notifications node
43. if (_notificationNode)
44. {
45. _notificationNode->visit(_renderer, Mat4::IDENTITY, false);
46. }
47.  
48. if (_displayStats)
49. {
50. showStats();
51. }
52.  
53. _renderer->render();
54. _eventDispatcher->dispatchEvent(_eventAfterDraw);
55.  
56. popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
57.  
58. _totalFrames++;
59.  
60. // swap buffers
61. if (_openGLView)
62. {
63. _openGLView->swapBuffers();
64. }
65.  
66. if (_displayStats)
67. {
68. calculateMPF();
69. }
70. }
通过调用setNextScene();来在每次绘制之前增加scene的引用计数,这样子在drawscene之后调用clear就能清楚了无用的内存ref对象:

 

 

01. void Director::setNextScene()
02. {
03. bool runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene) != nullptr;
04. bool newIsTransition = dynamic_cast<TransitionScene*>(_nextScene) != nullptr;
05.  
06. // If it is not a transition, call onExit/cleanup
07. if (! newIsTransition)
08. {
09. if (_runningScene)
10. {
11. _runningScene->onExitTransitionDidStart();
12. _runningScene->onExit();
13. }
14.  
15. // issue #709. the root node (scene) should receive the cleanup message too
16. // otherwise it might be leaked.
17. if (_sendCleanupToScene && _runningScene)
18. {
19. _runningScene->cleanup();
20. }
21. }
22.  
23. if (_runningScene)
24. {
25. _runningScene->release();
26. }
27. _runningScene = _nextScene;
28. _nextScene->retain();
29. _nextScene = nullptr;
30.  
31. if ((! runningIsTransition) && _runningScene)
32. {
33. _runningScene->onEnter();
34. _runningScene->onEnterTransitionDidFinish();
35. }
36. }

同时看一下Node::addChild的操作,能解决我们的疑惑,使用了addChild和Autorelease两种内存管理的区别:

 

 

1. void Node::addChild(Node* child, int localZOrder, const std::string &name)
2. {
3. CCASSERT(child != nullptr, "Argument must be non-nil");
4. CCASSERT(child->_parent == nullptr, "child already added. It can't be added again");
5.  
6. addChildHelper(child, localZOrder, INVALID_TAG, name, false);
7. }
01. void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)
02. {
03. if (_children.empty())
04. {
05. this->childrenAlloc();
06. }
07.  
08. this->insertChild(child, localZOrder);
09.  
10. if (setTag)
11. child->setTag(tag);
12. else
13. child->setName(name);
14.  
15. child->setParent(this);
16. child->setOrderOfArrival(s_globalOrderOfArrival++);
17.  
18. #if CC_USE_PHYSICS
19. // Recursive add children with which have physics body.
20. Scene* scene = this->getScene();
21. if (scene != nullptr && scene->getPhysicsWorld() != nullptr)
22. {
23. child->updatePhysicsBodyTransform(scene);
24. scene->addChildToPhysicsWorld(child);
25. }
26. #endif
27.  
28. if( _running )
29. {
30. child->onEnter();
31. // prevent onEnterTransitionDidFinish to be called twice when a node is added in onEnter
32. if (_isTransitionFinished) {
33. child->onEnterTransitionDidFinish();
34. }
35. }
36.  
37. if (_cascadeColorEnabled)
38. {
39. updateCascadeColor();
40. }
41.  
42. if (_cascadeOpacityEnabled)
43. {
44. updateCascadeOpacity();
45. }
46. }
这里要注意了,addChild中用到的vector不是std的vector,是CCVector,cocos2d重写的,

 

 

1. void pushBack(T object)
2. {
3. CCASSERT(object != nullptr, "The object should not be nullptr");
4. _data.push_back( object );
5. object->retain();
6. }


所以实际上我们看似使用AddChild交给cocos2d的渲染树帮助我们管理内存和启用AutoreleasePool形式管理内存是不相干,实际就相当于在以pool为载体的区域内建立了一批对象,根据需要去retian,或者挂载了渲染树上(每棵渲染树就是以scene为起点,addChild执行一次retian),每次渲染结束后都会清理一次内存池。所有在new之后,不调用Autorelease就要我们手动采取C++标准方式去管理内存;如果启用Autorelease的机制,因为调用Autorelease实在drawscene(cpu游戏主线和GPU渲染)之后,没有了之前存在可能申请了马上被清理的可能,所以不需要+-1,只需要在clear的时候判断是否为0。AddChild的区别在于挂载在渲染树上的节点_referenceCount初始为2,在执行一次clear之后就不在属于PoolManager的当中的AutoReleasePool,而是只属于渲染树啦。

 

渲染树的清理:

 

01. void Director::replaceScene(Scene *scene)
02. {
03. CCASSERT(_runningScene, "Use runWithScene: instead to start the director");
04. CCASSERT(scene != nullptr, "the scene should not be null");
05.  
06. if (scene == _nextScene)
07. return;
08.  
09. if (_nextScene)
10. {
11. if (_nextScene->isRunning())
12. {
13. _nextScene->onExit();
14. }
15. _nextScene->cleanup();
16. _nextScene = nullptr;
17. }
18.  
19. ssize_t index = _scenesStack.size();
20.  
21. _sendCleanupToScene = true;
22. _scenesStack.replace(index - 1, scene);
23.  
24. _nextScene = scene;
25. }

1. _nextScene->cleanup();
调用的是Node的cleanup,递归形式完成渲染树的清理。

 

提示: //利用静态函数实现每个pool创建自动添加至Manager,用的真妙!!C++只会越用越熟啊。

 

01. PoolManager* PoolManager::getInstance()
02. {
03. if (s_singleInstance == nullptr)
04. {
05. s_singleInstance = new PoolManager();
06. // Add the first auto release pool
07. new AutoreleasePool("cocos2d autorelease pool");
08. }
09. return s_singleInstance;
10. }
01. AutoreleasePool::AutoreleasePool(const std::string &name)
02. : _name(name)
03. #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
04. , _isClearing(false)
05. #endif
06. {
07. _managedObjectArray.reserve(150);
08. PoolManager::getInstance()->push(this);
09. }


所以每次执行完clear,Manager中除了渲染树和_referenceCount>1都会被清理掉;可是pool被清理之后_referenceCount>=1的内存就如渲染树般泄露了,这个时候就需要我用自己release(delete)了。为什么会出现这样子的情况呢,实际上源于早前兼容ObjectC的代码继承过来的管理机制,可能之后Pool和Autorelease的机制会被清理调用,只保存渲染树和手动retian、release。这和之后启用C++11 shared_ptr<typename>有关。因为3.2的pool实际上可有可无啦,完全可以让程序去实现了内存管理。

 

至于ref继承了clone接口,这个人尽皆知,就不分析啦。可惜目前我仍然无法搭建git环境,我最怕搭建环境了,比如花了4天去配置cocos2dx 2.x 和quick的win安卓环境,后来放弃了之后发现3.x完全是方便至极。虚拟机mac环境早搞定了,只剩sdk下载。

下面是源码推荐的用法:

 

01. // Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.
02. // This happens when 'autorelease/release' were not used in pairs with 'new/retain'.
03. //
04. // Wrong usage (1):
05. //
06. // auto obj = Node::create();   // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool.
07. // obj->autorelease();   // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first.
08. //
09. // Wrong usage (2):
10. //
11. // auto obj = Node::create();
12. // obj->release();   // Wrong: obj is an autorelease Ref, it will be released when clearing current pool.
13. //
14. // Correct usage (1):
15. //
16. // auto obj = Node::create();
17. //                     |-   new Node();     // `new` is the pair of the `autorelease` of next line
18. //                     |-   autorelease();  // The pair of `new Node`.
19. //
20. // obj->retain();
21. // obj->autorelease();  // This `autorelease` is the pair of `retain` of previous line.
22. //
23. // Correct usage (2):
24. //
25. // auto obj = Node::create();
26. // obj->retain();
27. // obj->release();   // This `release` is the pair of `retain` of previous line.



FROM: http://www.it165.net/pro/html/201410/23249.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值