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課堂微信公共平臺

相關文章
相關標籤/搜索