Cocos2d-x开发中Ref内存管理_C/C++_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > C/C++ > Cocos2d-x开发中Ref内存管理

Cocos2d-x开发中Ref内存管理

 2014/11/5 9:15:44  关东升  程序员俱乐部  我要评论(0)
  • 摘要:Ref类是Cocos2d-x根类,Cocos2d-x中的很多类都派生自它,例如,我们熟悉的节点类Node也派生自Ref。我们介绍Ref内存管理。内存引用计数Ref类设计来源于Cocos2d-iphone的CCObject类,在Cocos2d-x2.x中也叫CCObject类。因此Ref类的内存管理是参考Objective-C手动管理引用计数(ReferenceCount)而设计的。如图所示是内存引用计数原理示意图。每个Ref对象都有一个内部计数器,这个计数器跟踪对象的引用次数
  • 标签:开发 内存管理 内存

Ref类是Cocos2d-x根类,Cocos2d-x中的很多类都派生自它,例如,我们熟悉的节点类Node也派生自Ref。我们介绍Ref内存管理。
内存引用计数
Ref类设计来源于Cocos2d-iphone的CCObject类,在Cocos2d-x 2.x中也叫CCObject类。因此Ref类的内存管理是参考Objective-C手动管理引用计数(Reference Count)而设计的。
如图所示是内存引用计数原理示意图。?

每个Ref对象都有一个内部计数器,这个计数器跟踪对象的引用次数,被称为“引用计数”(Reference Count,简称RC)。当对象被创建时候,引用计数为1。为了保证对象的存在,可以调用retain函数保持对象,retain会使其引用计数加1,如果不需要这个对象可以调用release函数,release使其引用计数减1。当对象的引用计数为0的时候,引擎就知道不再需要这个对象了,就会释放对象内存。
引用计数实例如图所示,我们在ObjA中使用new等操作创建了一个Ref对象,这时候这个对象引用计数为1。然后在OjbB中使用retain函数保持Ref对象,这时引用计数为2。再然后ObjA中调用release函数,这时引用计数为1。在ObjB中调用release函数,这时引用计数为0。这个时候Ref对象就会由引擎释放。



在Ref类中相关函数有:retain()、release()、autorelease()和getReferenceCount()。其中autorelease()函数与release()函数类似,它会延后使引用计数减1,autorelease()我们稍后再介绍。getReferenceCount()函数返回当前的引用计数。


自动释放池
我们先看看下面的代码片段。

[html]?view plaincopy在CODE上查看代码片派生到我的代码片 ?
    class="dp-xml">
  1. XmlParser?*?XmlParser::createWithFile(const?char?*fileName)???
  2. {???
  3. ????XmlParser?*pRet?=?new?XmlParser();???
  4. ????//??①??
  5. ????return?pRet;???
  6. }???



上述代码XmlParser::createWithFile(const char *fileName)函数能够创建XmlParser对象指针并返回给调用者。根据我们前面介绍的C++使用new规则,在XmlParser::createWithFile函数中还应该释放对象的语句,如果没有,那么每次调用者调用XmlParser::createWithFile函数都会创建一个新对象,老的对象没有释放,就会造成内存泄漏。但是如果我们在第①行代码,添加释放语句pRet->release(),那么问题可能会更严重,返回的对象可能已经被释放,返回的可能是一个野指针。
自动释放池(AutoReleasePool)正是为此而设计,自动释放池也是来源于Objective-C,Cocos2d-x中维护AutoreleasePool对象,它能够管理即将释放的对象池。我们在第①可以使用pRet->autorelease()语句,autorelease()函数将对象放到自动释放池,但对象的引用计数并不马上减1,而是要等到一个消息循环结束后减1,如果引用计数为0(即,没有被其它类或Ref对象retain),则释放对象,在此之前对象并不会释放。
消息循环是游戏循环一个工作职责,消息循环说到底还是游戏循环,消息循环是接收事件,并处理事件。自动释放池的生命周期也是由消息循环管理的。如图所示,图中“圈圈”是消息循环周期,它的一个工作职责是维护自动释放池创建和销毁。每次为了处理新的事件,Cocos2d-x引擎都会创建一个新的自动释放池,事件处理完成后,就会销毁这个池,池中对象的引用计数会减1,如果这个引用计数会减0,也就是没有被其它类或Ref对象retain,则释放对象,否则这个对象不会释放,在这次销毁池过程中“幸存”下来,它被转移到下一个池中继续生存。




下面我们看一个实例,下面代码是13.2.2一节的实例HelloWorldScene.cpp代码:

[html]?view plaincopy在CODE上查看代码片派生到我的代码片 ?
  1. bool?HelloWorld::init()??
  2. {??
  3. ????if?(?!Layer::init()?)??
  4. ????{??
  5. ????????return?false;??
  6. ????}??
  7. ??
  8. ??
  9. ????Size?visibleSize?=?Director::getInstance()->getVisibleSize();??
  10. ????Vec2?origin?=?Director::getInstance()->getVisibleOrigin();??
  11. ??
  12. ??
  13. ????auto?goItem?=?MenuItemImage::create(??
  14. ????????"go-down.png",??
  15. ????????"go-up.png",??
  16. ????????CC_CALLBACK_1(HelloWorld::menuCloseCallback,?this));??
  17. ??
  18. ??
  19. ????goItem->setPosition(Vec2(origin.x?+?visibleSize.width?-?goItem->getContentSize().width/2?,??
  20. ????????origin.y?+?goItem->getContentSize().height/2));??
  21. ??
  22. ??
  23. ????auto?menu?=?Menu::create(goItem,?NULL);?????????????????????????????????①??
  24. ????menu->setPosition(Vec2::ZERO);??
  25. ????this->addChild(menu,?1);?????????????????????????????????????????????②??
  26. ??
  27. ??
  28. ????this->list??=?__Array::createWithCapacity(MAX_COUNT);??????????????????????????????
  29. ????this->list->retain();?????????????????????????????????????????????????③??
  30. ??
  31. ??
  32. ????for(int?i?=?0;i?<?MAX_COUNT;?++i){??
  33. ????????Sprite*?sprite?=?Sprite::create("Ball.png");??
  34. ????????this->list->addObject(sprite);????????????????????????????????????????????④??
  35. ????}??
  36. ??
  37. ??
  38. ????return?true;??
  39. }??
  40. ??
  41. ??
  42. ??
  43. ??
  44. void?HelloWorld::menuCloseCallback(Ref*?pSender)??
  45. {??
  46. ????Ref*?obj?=?NULL;??
  47. ????log("list->count()?=?%d",this->list->count());?????????????????????????????????⑤??
  48. ????Size?visibleSize?=?Director::getInstance()->getVisibleSize();??
  49. ??
  50. ??
  51. ????CCARRAY_FOREACH(this->list,?obj)?{??
  52. ??????????
  53. ????????Sprite*?sprite?=?(Sprite*)obj;??????????????????????????????????????????⑥??
  54. ??
  55. ??
  56. ????????int?x?=?CCRANDOM_0_1()?*?visibleSize.width;??
  57. ????????int?y?=?CCRANDOM_0_1()?*?visibleSize.height;??
  58. ??
  59. ??
  60. ????????sprite->setPosition(?Vec2(x,?y)?);??
  61. ????????this->removeChild(sprite);??
  62. ????????this->addChild(sprite);??
  63. ????}??
  64. ??
  65. ??
  66. }??
  67. ??
  68. ??
  69. HelloWorld::~HelloWorld()?????
  70. {??
  71. ????this->list->removeAllObjects();??
  72. ????CC_SAFE_RELEASE_NULL(this->list);????????????????????????????????????????⑦??
  73. }??



在上述代码中我们需要关注两个对象(Menu和__Array)创建。第①行auto menu = Menu::create(goItem, NULL)通过create静态工厂创建对象,关于静态工厂的创建原理我们会在下一节介绍。如果我们不采用第②行的this->addChild(menu, 1)语句将menu 对象放入到当前层(HelloWorld)的子节点列表中,那么这个menu对象就会在当前消息循环结束的时候被释放。调用this->addChild(menu, 1)语句会将它的生命周期持续到HelloWorld层释放的时候,而不会在当前消息循环结束释放。
菜单、层等节点对象可以调用addChild函数,使得其生命延续。而且__Array和__Dictionary等Ref对象没有调用addChild函数保持,我们需要显式地调用retain函数保持它们,以便延续其生命。如代码第③行this->list->retain(),list就是一个__Array指针类型的成员变量,如果没有第③行语句,那么在第⑤行代码this->list->count()程序就会出错,因为这个时候list对象已经释放了。采用了retain保持的成员变量,一定要release(或autorelease),retain和release(或autorelease)一定是成对出现的。我们可以在析构函数~HelloWorld()中调用release释放,而第⑦行代码CC_SAFE_RELEASE_NULL(this->list)就是实现这个目的,其中CC_SAFE_RELEASE_NULL宏作用如下:
list->release();
list = nullptr;
可见CC_SAFE_RELEASE_NULL宏不仅仅释放对象,还将它的指针设置为nullprt[],也样可以防止野指针。
上述代码还有一个非常重要的关于内存的问题,我们在HelloWorld::init()函数中创建了很多Sprite对象,通过第④行代码this->list->addObject(sprite)将它们放到list容器对象中,它们没有调用addChild函数也没有显式retain函数,然而在当前消息循环结束后它们还是“存活”的,所以在第⑥行Sprite* sprite = (Sprite*)obj中,取出的对象是有效的。这个原因__Array和__Dictionary等容器对象的add相关函数可以使添加对象引用计数加1,相反的remove相关函数可以使添加对象引用计数减1。


Ref内存管理规则
下面我们给出使用Ref对象时候,内存管理一些基本规则:


1、在使用Node节点对象时候,addChild函数可以保持Node节点对象,使引用计数加1。通过removeChild函数移除Node节点对象,使引用计数减1。它们都是隐式调用的,我们不需要关心它们的内存管理。这也正是为什么在前面的章节中我们无数次地使用了Node节点对象,而从来都没有担心过它们内存问题。


2、如果是__Array和__Dictionary等容器对象,可以通过它们add相关函数添加元素会使引用计数加1,相反的remove相关函数删除元素会使引用计数减1。但是前提是__Array和__Dictionary等容器对象本身不没有被释放。


3、如果不属于上面提到的Ref对象,需要保持引用计数,可以显式调用retain函数使引用计数加1,然后显式调用release(或autorelease)函数使引用计数减1。


4、每个 retain函数一定要对应一个 release函数或一个 autorelease函数。


5、release函数使得对象的引用计数马上减1,这是所谓的“斩立决”,但是是否真的释放掉内存要看它的引用计数是否为0。autorelease函数只是在对象上做一个标记,等到消息循环结束的时候再减1,这是所谓的“秋后问斩”,在“秋天”没有到来之前,它的内存一定没有释放,可以安全使用,但是“问斩”之后,是否真的释放掉内存要看它的引用计数是否为0。因此无论是那一种方法,引用计数是为0才是释放对象内存的条件。下面的代码是Ref类的release函数,通过这段代码可以帮助我们理解引用计数。

[html]?view plaincopy在CODE上查看代码片派生到我的代码片 ?
  1. void?Ref::release()??
  2. {??
  3. ????CCASSERT(_referenceCount?>?0,?"reference?count?should?greater?than?0");??
  4. ????--_referenceCount;??
  5. ??????
  6. ????if?(_referenceCount?==?0)??
  7. ????{??
  8. #if?defined(COCOS2D_DEBUG)?&&?(COCOS2D_DEBUG?>?0)??
  9. ????????auto?poolManager?=?PoolManager::getInstance();??
  10. ????????if?(!poolManager->getCurrentPool()->isClearing()?&&?poolManager->isObjectInPools(this))??
  11. ????????{??
  12. ????????????//?Trigger?an?assert?if?the?reference?count?is?0?but?the?Ref?is?still?in?autorelease?pool.??
  13. ????????????//?This?happens?when?'autorelease/release'?were?not?used?in?pairs?with?'new/retain'.??
  14. ????????????//??
  15. ????????????//?Wrong?usage?(1):??
  16. ????????????//??
  17. ????????????//?auto?obj?=?Node::create();???//?Ref?=?1,?but?it's?an?autorelease?Ref?which?means?it?was?in?the?autorelease?pool.??
  18. ????????????//?obj->autorelease();???//?Wrong:?If?you?wish?to?invoke?autorelease?several?times,?you?should?retain?`obj`?first.??
  19. ????????????//??
  20. ????????????//?Wrong?usage?(2):??
  21. ????????????//??
  22. ????????????//?auto?obj?=?Node::create();??
  23. ????????????//?obj->release();???//?Wrong:?obj?is?an?autorelease?Ref,?it?will?be?released?when?clearing?current?pool.??
  24. ????????????//??
  25. ????????????//?Correct?usage?(1):??
  26. ????????????//??
  27. ????????????//?auto?obj?=?Node::create();??
  28. ????????????//?????????????????????|-???new?Node();?????//?`new`?is?the?pair?of?the?`autorelease`?of?next?line??
  29. ????????????//?????????????????????|-???autorelease();??//?The?pair?of?`new?Node`.??
  30. ????????????//??
  31. ????????????//?obj->retain();??
  32. ????????????//?obj->autorelease();??//?This?`autorelease`?is?the?pair?of?`retain`?of?previous?line.??
  33. ????????????//??
  34. ????????????//?Correct?usage?(2):??
  35. ????????????//??
  36. ????????????//?auto?obj?=?Node::create();??
  37. ????????????//?obj->retain();??
  38. ????????????//?obj->release();???//?This?`release`?is?the?pair?of?`retain`?of?previous?line.??
  39. ????????????CCASSERT(false,?"The?reference?shouldn't?be?0?because?it?is?still?in?autorelease?pool.");??
  40. ????????}??
  41. #endif??
  42. ????????delete?this;??
  43. ????}??
  44. }??




6、一个对象调用autorelease函数,它就会将对象放到自动释放池里,它生命周期自动释放池生命周期息息相关,池在消息循环结束的时候会释放,池会调用池内对象release函数,使得它们的引用计数减1。下面的代码是AutoreleasePool类的清除函数clear(),通过这段代码可以帮助我们理解自动释放池机制。

[html]?view plaincopy在CODE上查看代码片派生到我的代码片 ?
  1. void?AutoreleasePool::clear()??
  2. {??
  3. #if?defined(COCOS2D_DEBUG)?&&?(COCOS2D_DEBUG?>?0)??
  4. ????_isClearing?=?true;??
  5. #endif??
  6. ????for?(const?auto?&obj?:?_managedObjectArray)??
  7. ????{??
  8. ????????obj->release();??
  9. ????}??
  10. ????_managedObjectArray.clear();??
  11. #if?defined(COCOS2D_DEBUG)?&&?(COCOS2D_DEBUG?>?0)??
  12. ????_isClearing?=?false;??
  13. #endif??
  14. }??

?

?

?

更多内容请关注国内第一本Cocos2d-x 3.2版本图书《Cocos2d-x实战:C++卷》 本书交流讨论网站:http://www.cocoagame.net
更多精彩视频课程请关注智捷课堂Cocos课程:http://v.51work6.com 欢迎加入Cocos2d-x技术讨论群:257760386 欢迎关注智捷iOS课堂微信公共平台

上一篇: Ubuntu 中使用eclipse 部署一个python项目的过程 下一篇: 没有下一篇了!
发表评论
用户名: 匿名