`

Cocos2d-x开发中Ref内存管理

c++ 
阅读更多

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上查看代码片派生到我的代码片
 
  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. }  

 

 

 

更多内容请关注最新Cocos图书《Cocos2d-x实战 C++卷》

本书交流讨论网站:http://www.cocoagame.net

更多精彩视频课程请关注智捷课堂Cocos课程:http://v.51work6.com
欢迎加入Cocos2d-x技术讨论群:257760386


《Cocos2d-x实战 C++卷》现已上线,各大商店均已开售:

京东:http://item.jd.com/11584534.html

亚马逊:http://www.amazon.cn/Cocos2d-x%E5%AE%9E%E6%88%98-C-%E5%8D%B7-%E5%85%B3%E4%B8%9C%E5%8D%87/dp/B00PTYWTLU

当当:http://product.dangdang.com/23606265.html

互动出版网:http://product.china-pub.com/3770734

 

《Cocos2d-x实战 C++卷》源码及样章下载地址:

源码下载地址:http://51work6.com/forum.php?mod=viewthread&tid=1155&extra=page%3D1 

样章下载地址:http://51work6.com/forum.php?mod=viewthread&tid=1157&extra=page%3D1

欢迎关注智捷iOS课堂微信公共平台

分享到:
评论

相关推荐

    Cocos2d-X 3.4版-游戏继续,游戏重新开始,回到主界面的实现《赵云要格斗》

    在Cocos2d-X中,这通常通过保存和恢复游戏状态来实现。你可以创建一个专门的数据结构(如自定义类)来存储游戏的关键信息,如角色位置、生命值等。当游戏暂停时,将这些信息序列化并保存到本地存储;当需要继续时,...

    Cocos2D-X开发学习笔记-渲染框架之菜单类的使用示例

    在Cocos2D-X中,`cocos2d::Menu` 类是用来创建和管理菜单的,它可以包含多个`MenuItem`子对象,每个`MenuItem`代表一个可点击的元素。`MenuItem`有多种类型,如`MenuItemImage`用于显示图片,`MenuItemLabel`用于...

    cocos2d-x绑定ccb文件个人实现的例子

    本示例将详细介绍如何在Cocos2d-x中实现个人化的`ccb`文件绑定。 首先,我们需要了解Cocos2d-x和CocosBuilder的基本概念。Cocos2d-x是一个开源的2D游戏引擎,广泛应用于移动平台,如iOS、Android和Windows Phone。...

    cocos2d-x中ScrollView的使用

    接下来,我们将深入探讨如何在cocos2d-x中使用ScrollView以及相关知识点。 首先,ScrollView的实现基于cocos2d-x的ui模块,它包含了CCScrollView类。这个类提供了垂直滚动、水平滚动或两者混合的滚动功能。为了创建...

    cocos2d-x中模态对话框的简单实现

    在本教程中,我们将深入探讨如何在cocos2d-x中实现模态对话框。 首先,了解模态对话框的基本概念。模态对话框是一种阻塞式界面,当它显示时,用户必须先关闭对话框才能继续与应用程序的其他部分进行交互。这使得...

    cocos2d-x ScrollView使用

    在cocos2d-x中,ScrollView是一个重要的组件,它允许用户在屏幕内滚动查看超出可视区域的内容,这对于长列表或者大图像的展示非常有用。本篇将详细讲解cocos2d-x中ScrollView的使用方法。 1. **ScrollView简介** ...

    cocos2d-x中ScrollView的实现

    本篇文章将深入探讨cocos2d-x中`ScrollView`的实现原理、使用方法以及相关的实践技巧。 一、`ScrollView`概述 `ScrollView`是cocos2d-x提供的视图容器,它包含了可滚动的内容,可以垂直或水平滚动。它内部包含一个`...

    cocos2d-x学习笔记(6)-- menu.rar

    在cocos2d-x中,有多种类型的MenuItem供开发者选择,如MenuItemImage用于展示图片,MenuItemLabel用于显示文本,MenuItemFont用于自定义字体的文本,还有MenuItemSprite等。创建MenuItem的基本步骤如下: 1. 首先,...

    Cocos2d-x 3.x入门教程(二):Node节点类

    Node类在Cocos2d-x中有多重要呢?任何需要画在屏幕上的对象都是节点类。最常用的节点类包括场景类(Scene)、布景层类(Layer)、精灵类(Sprite)、菜单类(Menu)。Node类的主要功能如下: 1.每个节点都可以含有子

    quick-cocos2dx用lua调用自定义c++

    这个框架结合了Cocos2d-x的高性能与Lua的易用性,使得游戏开发更为高效。本教程将详细介绍如何在Quick-Cocos2dx中通过Lua调用自定义的C++类,帮助开发者实现更灵活的游戏编程。 首先,理解Quick-Cocos2dx的基本架构...

    cocostudio GUI的控件事件响应示例

    本示例将聚焦于Cocostudio GUI系统中的控件事件响应机制,通过实际操作帮助开发者理解和掌握如何在Cocos2d-x环境中加载并处理这些事件。 首先,我们要明确Cocostudio的核心功能:它允许开发者在图形界面下设计和...

    cocos2dx 菜单例子

    本篇将详细讲解在Cocos2d-x中如何创建和使用菜单(Menu),以及相关的实践例子。 在Cocos2d-x中,菜单(Menu)是一个特殊的层(Layer),用于组织和展示一系列可点击的按钮(MenuItem)。这些按钮可以响应用户的...

    如何使用cocos自动绑定工具实现lua调用c

    在游戏开发领域,尤其是在使用Cocos2d-x框架进行开发时,经常会遇到需要让脚本语言(如Lua)能够调用C++代码的需求。这不仅能充分利用C++的强大性能,还能保持脚本语言的灵活性。本文将详细介绍如何通过Cocos2d-x...

    cocos2dx中lua扩展(ccb执行完回调函数扩展)

    在Cocos2d-x中,我们通常会为C++类创建对应的Lua绑定,以便于在Lua中使用。在这个案例中,我们需要为`CCBReader`类添加一个新方法,用于设置动画完成后的回调。这个方法可能类似于这样: ```cpp int lua_cocos2dx_...

    战场、地图与单位文档1

    在Cocos2d-x中,`cocos2d::Ref`类是基础类,它支持引用计数和自动释放池的内存管理。`cocos2d::Layer`类是游戏场景的基本构建块,可以包含多个游戏元素,如精灵、标签、地图等。`cocos2d::Sprite`类用于创建和管理2D...

    lua 绑定c++ 详细教程!

    随着跨平台游戏开发框架Cocos2d-x的发展,Lua脚本语言因为其轻量级、易嵌入的特点而在游戏开发领域得到了广泛的应用。为了更好地利用Lua的优势同时发挥C++的强大功能,Cocos2d-x提供了Lua与C++之间的绑定技术。在...

    FudanCraft需求与设计报告1

    FudanCraft是一款基于Cocos2d-x游戏引擎开发的游戏,其需求与设计报告涵盖了多个关键领域,包括游戏界面、内存管理、数据结构、算法以及网络通信策略。以下是各领域的详细说明: 1. **游戏界面与用户交互**: - ...

Global site tag (gtag.js) - Google Analytics