Cocos2d-x开发中Ref内存管理

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

html

每一个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上查看代码片派生到个人代码片app

  1. XmlParser * XmlParser::createWithFile(const char *fileName)   iphone

  2. {   函数

  3.     XmlParser *pRet = new XmlParser();   网站

  4.     //  ①  this

  5.     return pRet;   spa

  6. }   .net



上述代码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对象时候,内存管理一些基本规则:


一、在使用Node节点对象时候,addChild函数能够保持Node节点对象,使引用计数加1。经过removeChild函数移除Node节点对象,使引用计数减1。它们都是隐式调用的,咱们不须要关心它们的内存管理。这也正是为何在前面的章节中咱们无数次地使用了Node节点对象,而历来都没有担忧过它们内存问题。


二、若是是__Array和__Dictionary等容器对象,能够经过它们add相关函数添加元素会使引用计数加1,相反的remove相关函数删除元素会使引用计数减1。可是前提是__Array和__Dictionary等容器对象自己不没有被释放。


三、若是不属于上面提到的Ref对象,须要保持引用计数,能够显式调用retain函数使引用计数加1,而后显式调用release(或autorelease)函数使引用计数减1。


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


五、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. }  




六、一个对象调用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课堂微信公共平台

相关文章
相关标签/搜索