Cosos2dx中的內存管理機制

Why?

內存管理一直是個很重要的事情绝页,需要考慮分配和回收的時機娃惯,千萬不能出現(xiàn)內存泄漏,因為手機上內存本來就有限坠陈。內存管理的核心就是動態(tài)分配的內存及時回收。cocos2dx是從cocos2d-iphone發(fā)展過來的捐康,使用的引用計數(shù)的機制進行管理仇矾。了解了cocos中的內存管理機制,就可以明確的了解在什么時候retian在什么時候release解总。我盡量只說關鍵的地方贮匕,不貼大量的代碼。

引用計數(shù)

剛開始了解cocos的時候花枫,看了一些書刻盐,書上說cocos中是使用的引用計數(shù)的機制進行管理內存。當時想了下劳翰,很好理解啊敦锌,每一幀去檢查一下每個對象的引用計數(shù),如果是0則釋放掉佳簸。最近深入看了一下源碼乙墙,發(fā)現(xiàn)不是每幀去檢查每個對象,每個對象只會被檢查一次生均,后面就在恰當?shù)臅r機進行釋放听想。

自動釋放池

AutoreleasePool里面就是封裝了一個vector,并對vector中的對象進行管理马胧。

自動釋放池管理

PoolManager汉买,此類是對自動釋放池的管理,看到有這個管理類佩脊,就知道自動釋放池可能不止一個蛙粘,是的,自動釋放池可以創(chuàng)建多個威彰,都由PoolManager這個類管理出牧。PoolManager使用棧的操作來管理自動釋放池。

Ref類

此類中有個變量保存引用計數(shù)抱冷,繼承此類崔列,就可以很方便的加入自動釋放池中管理。引用計數(shù)初始的值為1

Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1

一個Node被管理的過程

1:創(chuàng)建一個node

Node* node = Node::create();

此時的node的引用計數(shù)是1
調用了Node的create工廠方法進行創(chuàng)建,查看create方法的實現(xiàn)

Node * Node::create()
{
    Node * ret = new (std::nothrow) Node();
    if (ret && ret->init())
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    return ret;
}

node調用了autorelease方法赵讯,此方法為Ref類的方法

Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

其實是把當前的node放到了自動釋放池中進行管理盈咳。PoolManager調用getInstance的方法獲取實例,查看里面的實現(xiàn)

PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        s_singleInstance = new (std::nothrow) PoolManager();
        // Add the first auto release pool
        new AutoreleasePool("cocos2d autorelease pool");
    }
    return s_singleInstance;
}

可以看到自動釋放池也是在此時創(chuàng)建的边翼。以前引擎版本的自動釋放池是在引擎初始化的時候創(chuàng)建的鱼响。
PoolManager類的getCurrentPool方法返回的是當前的自動釋放池

AutoreleasePool* PoolManager::getCurrentPool() const
{
    return _releasePoolStack.back();
}

_releasePoolStack是一個vector,但是現(xiàn)在當成棧來用组底。

2:在mainLoop中進行處理

mainLoop是上篇文章說的主循環(huán)的操作丈积,每一幀都會調用

void Director::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (_restartDirectorInNextLoop)
    {
        _restartDirectorInNextLoop = false;
        restartDirector();
    }
    else if (! _invalid)
    {
        drawScene();
     
        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

此處調用了PoolManager::getInstance()->getCurrentPool()->clear()查看clear中的代碼

std::vector<Ref*> releasings;
    releasings.swap(_managedObjectArray);
    for (const auto &obj : releasings)
    {
        obj->release();
    }

_managedObjectArray為自動釋放池中的對象容器;releasings是個空的容器债鸡。
經過releasings.swap(_managedObjectArray)這個操作之后_managedObjectArray就是空的了江滨,就是說加入了自動釋放池中的對象只會被管理一次,后面不會再被管理了厌均。
對releasings中的對象(其實就是剛才內存釋放池中的對象)進行遍歷唬滑,進行release操作,此為Ref類中的方法

void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    --_referenceCount;

    if (_referenceCount == 0)
    {
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
        auto poolManager = PoolManager::getInstance();
        if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
        {
            // Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.
            // This happens when 'autorelease/release' were not used in pairs with 'new/retain'.
            //
            // Wrong usage (1):
            //
            // auto obj = Node::create();   // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool.
            // obj->autorelease();   // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first.
            //
            // Wrong usage (2):
            //
            // auto obj = Node::create();
            // obj->release();   // Wrong: obj is an autorelease Ref, it will be released when clearing current pool.
            //
            // Correct usage (1):
            //
            // auto obj = Node::create();
            //                     |-   new Node();     // `new` is the pair of the `autorelease` of next line
            //                     |-   autorelease();  // The pair of `new Node`.
            //
            // obj->retain();
            // obj->autorelease();  // This `autorelease` is the pair of `retain` of previous line.
            //
            // Correct usage (2):
            //
            // auto obj = Node::create();
            // obj->retain();
            // obj->release();   // This `release` is the pair of `retain` of previous line.
            CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
        }
#endif

#if CC_ENABLE_SCRIPT_BINDING
        ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
        if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
        {
            pEngine->removeObjectProxy(this);
        }
#endif // CC_ENABLE_SCRIPT_BINDING

#if CC_REF_LEAK_DETECTION
        untrackRef(this);
#endif
        delete this;
    }
}

大家可以看到棺弊,把引用計數(shù)減1晶密,如果此時引用計數(shù)為0,則釋放此Ref對象模她。
剛才創(chuàng)建的node稻艰,此時就會被釋放掉了。如果使用了成員變量或者全局變量指向了剛才的node侈净,那么剛才的node就是個野指針了尊勿。

3:添加到場景中的Node的引用計數(shù)情況

創(chuàng)建了的node添加到場景(其實也是個Node)中,此時引用計數(shù)就會+1用狱,變成2

Node* node = Node::create();        // 此時node的引用計數(shù)為1
Layer* layer = Layer::create();
layer->addChild(node);                // 此時node的引用計數(shù)為2

為什么addChild了以后引用計數(shù)就變成2了呢运怖,來看看代碼

void Node::addChild(Node* child, int localZOrder, const std::string &name)
    調用addChildHelper(child, localZOrder, INVALID_TAG, name, false)
        調用Node::insertChild(Node* child, int z)
            調用_children.pushBack(child)
                調用_data.push_back( object );
                    object->retain();
                        調用++_referenceCount;

原來Node(Layer繼承自Node)內部有個CCVector,每次添加Ref到CCVector都會retain一下夏伊,會增加一次引用計數(shù)。
相應的在removechild的時候會調用release吻氧,此時引用計數(shù)減一溺忧,如果此時引用計數(shù)為0了,則立刻釋放盯孙,就不用在自動釋放池中釋放了鲁森。所以自動釋放池只要管理一次就可以了。

4:如何讓Ref不被釋放

其實很簡單振惰,retain一下就可以了歌溉。
調用addChild方法會增加一次引用計數(shù)。有些Ref對象是不用添加到場景中的,比如CCArray痛垛,只是用來對場景中的元素進行管理草慧,那么在創(chuàng)建一個CCArray對象以后,主動retain一下匙头,就不會被自動釋放池清除掉漫谷。但是在場景結束時,要relase掉蹂析,不然就會出現(xiàn)內存泄漏舔示。

自動釋放池的嵌套

如果創(chuàng)建了大量的Ref變量,那么在mainLoop的時候就有可能出現(xiàn)釋放時間過長电抚,導致下一次繪圖間隔太久惕稻,導致界面上卡頓。如果有此種情況蝙叛,可以考慮自己創(chuàng)建自動釋放池來存放Ref變量俺祠,用完就釋放掉。
此時PoolManager的管理功能就派上用場了甥温。下面來看看這個過程锻煌。
使用場景:創(chuàng)建了大量的String(cocos中封裝的字符類,非標準庫中的類)對象對字符串進行操作姻蚓,由于字符對象是不用加載到場景中的宋梧,所以在使用完成以后在本幀結束后就會被釋放掉。

AutoreleasePool* pool = new AutoreleasePool("My release pool");        // 創(chuàng)建我們自己的auto release pool

String* string1 = String::createWithFormate("nihao%d", 1);
...
...
String* string1000 = String::createWithFormate("nihao%d", 1000);

delete pool;                // 釋放我們自己創(chuàng)建auto release pool

分析一下上述流程:
1:創(chuàng)建了一個我們自己的自動釋放池名字叫:My release pool狰挡。查看AutoreleasePool的構造函數(shù)

AutoreleasePool::AutoreleasePool(const std::string &name)
: _name(name)
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{
    _managedObjectArray.reserve(150);
    PoolManager::getInstance()->push(this);
}

PoolManager會將我們剛剛創(chuàng)建的自動釋放池加入到內部的棧中捂龄。此時通過getCurrentPool獲取的就是剛才創(chuàng)建的自動釋放池。

2:創(chuàng)建字符串變量指針加叁,createWithFormate里面調用了autorelease函數(shù)倦沧,此時這些變量的指針都會添加到我們剛才創(chuàng)建的自動釋放池中。

3:刪除自動釋放池它匕。查看AutoreleasePool的析構函數(shù)展融。

AutoreleasePool::~AutoreleasePool()
{
    CCLOGINFO("deallocing AutoreleasePool: %p", this);
    clear();
    
    PoolManager::getInstance()->pop();
}

此時會先調用clear函數(shù)釋放池中的指針。然后PoolManager會將當前的自動釋放池從內部的棧中移除豫柬。此時PoolManager調用getCurrentPool返回的就是默認的自動釋放池告希。

總結:

1:自動釋放池對每個Ref對象指針管理一次,后面就在恰當?shù)臅r機進行釋放烧给。
2:CCVetctor在添加Ref對象指針的時候會retain一次燕偶,在移除的時候會release一次。
3:自動釋放池可以嵌套使用础嫡。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末指么,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伯诬,老刑警劉巖晚唇,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異姑廉,居然都是意外死亡缺亮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門桥言,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萌踱,“玉大人,你說我怎么就攤上這事号阿〔⑼遥” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵扔涧,是天一觀的道長园担。 經常有香客問我,道長枯夜,這世上最難降的妖魔是什么弯汰? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮湖雹,結果婚禮上咏闪,老公的妹妹穿的比我還像新娘。我一直安慰自己摔吏,他們只是感情好鸽嫂,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著征讲,像睡著了一般据某。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诗箍,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天癣籽,我揣著相機與錄音,去河邊找鬼滤祖。 笑死才避,一個胖子當著我的面吹牛,可吹牛的內容都是我干的氨距。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼棘劣,長吁一口氣:“原來是場噩夢啊……” “哼俏让!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤首昔,失蹤者是張志新(化名)和其女友劉穎寡喝,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勒奇,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡预鬓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赊颠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片格二。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖竣蹦,靈堂內的尸體忽然破棺而出顶猜,到底是詐尸還是另有隱情,我是刑警寧澤痘括,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布长窄,位于F島的核電站,受9級特大地震影響纲菌,放射性物質發(fā)生泄漏挠日。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一翰舌、第九天 我趴在偏房一處隱蔽的房頂上張望嚣潜。 院中可真熱鬧,春花似錦灶芝、人聲如沸郑原。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽犯犁。三九已至,卻和暖如春女器,著一層夾襖步出監(jiān)牢的瞬間酸役,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工驾胆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涣澡,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓丧诺,卻偏偏與公主長得像入桂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子驳阎,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容

  • 29.理解引用計數(shù) Objective-C語言使用引用計數(shù)來管理內存抗愁,也就是說馁蒂,每個對象都有個可以遞增或遞減的計數(shù)...
    Code_Ninja閱讀 1,475評論 1 3
  • 內存管理是程序在運行時分配內存、使用內存蜘腌,并在程序完成時釋放內存的過程沫屡。在Objective-C中,也被看作是在眾...
    蹲瓜閱讀 3,025評論 1 8
  • iOS內存管理 概述 什么是內存管理 應用程序內存管理是在程序運行時分配內存(比如創(chuàng)建一個對象,會增加內存占用)與...
    蚊香醬閱讀 5,701評論 8 119
  • 內存管理 簡述OC中內存管理機制撮珠。與retain配對使用的方法是dealloc還是release沮脖,為什么?需要與a...
    丶逐漸閱讀 1,950評論 1 16
  • UIButton 上默認是圖片在左文字在右芯急,而大多數(shù)情況這樣默認的的顯示形式都不能滿足我們的需求勺届,改變它們的原理很...
    GreenC閱讀 3,317評論 0 0