Cocos2d-X3.0 刨根問底(四)----- 內存管理源碼分析


        本系列文章發表以來得到了很多朋友的關注,小魚在這里謝謝大家對我的支持,我會繼續努力的,最近更新慢了一點,因為我老婆流產了抽了很多時間來照顧她希望大家諒解,並在此預祝我老婆早日康復。

        上一篇,我們完整的分析了Director這個類,並提到了Director這個繼承了Ref這個類,大致看了一下Ref這個類,是一個關於引用計數的類,從而我們可以推斷Cocos2d-x用了一種引用計數的方式來管理內存對象,這一章我們刨根問底Cocos2d-x是如何實現內存管理及我們如何在實際項目開發中應用Cocos2d-x的內存管理。

        打開CCRef.h文件,還好,這個文件並不大只有167行,整體瀏覽一遍,這個文件里一共定義了兩個類 Clonable類和Ref類。

class CC_DLL Clonable
class CC_DLL Ref

        Ref類是引用計數類,那么從命名上可以初步判斷Clonable類應該是對象拷貝相關內容的類,不要跑題,我們是沖着Ref類來的先從這里下手,稍后再回過頭來看Clonable是個什么球玩意。

        Ref類的完整定義

class CC_DLL Ref
{
public:
    /**
     * 增加一次引用計數
     */
    void retain();
    
    /**
     * 釋放一次計數,具體里面怎么釋放的,下面我們跟定義的代碼做分析
     */
    void release();

    /**
     * 自動釋放
     */
    Ref* autorelease();

    /**
     * 得到當前對象的引用計數的值,也是是被引用了多少次
     */
    unsigned int getReferenceCount() const;
    
protected:
    /**
     * Constructor
     *
     * The Ref's reference count is 1 after construction.
     * @js NA
     */
    Ref();
    
public:
    /**
     * @js NA
     * @lua NA
     */
    virtual ~Ref();
    
protected:
    /// 用來記錄引用次數的變量值
    unsigned int _referenceCount;
    
    friend class AutoreleasePool;
    
#if CC_ENABLE_SCRIPT_BINDING
public:
    /// 對象的ID
    unsigned int        _ID;
    /// 這個變量這里猜測是lua腳本中引用的ID
    int                 _luaID;
#endif
};

 

看了這個類的頭文件定義,目前還只能了解了大概意思,Ref這個類用一個變量 _referenceCount來記錄對象被引用了多少次,是否應該被釋放,並且有一個增加引用 的方法 retain 和兩個釋放的方法 autorelease 和 release

還出現了一個新的類,為Ref的友元類 AutoreleasePool 從命名上斷定這是一個 自動釋放對象池,看來這個Ref類雖然代碼量不大但並不簡單。下面我們逐個分析這個類的幾個方法,

在看實現源碼時先說明一下,里面會有很多宏定義,這些宏定義都在

#include "CCPlatformMacros.h"
#include "ccConfig.h"

這兩個文件里,碰到陌生的就查看一下這兩個文件里的定義就可以了,由於Cocos2d-x的命名很規范,很多宏定義都可以通過命名來得知它的含義,這也就是前輩們的一句老話,最好的注釋就是沒有注釋。

這里和大家分享一下讀源碼的經驗,看別人的類的時候往往不要按着類定義的函數順序去閱讀,要根據邏輯思維的順序去閱讀。

這個Ref類我們從構造函數開始(大多數類都要從構造函數開始讀)

再看一下 Ref的構造函數聲明

protected:
    /**
     * Constructor
     *
     * The Ref's reference count is 1 after construction.
     * @js NA
     */
    Ref();

恩,沒錯,Ref的構造函數聲明為 protected 的訪問權限,那么這個Ref類是不可以被直接實例化的 只能有子類來實例化這個對象。(可能有些新手讀者不理解什么叫實例化,可以簡單理解實例化就是被 new現來的對象,不能實例化就是不能用new來創建對象)

 

Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
{
#if CC_ENABLE_SCRIPT_BINDING
    static unsigned int uObjectCount = 0;
    _luaID = 0;
    _ID = ++uObjectCount;
#endif
}

_referencecount 被初始化為 1 不難理解,當對象被創建的時候,肯定要用1次,所以這個引用計數必然在創建的時候為1

CC_ENABLE_SCRIPT_BINDING  支持腳本綁定,cocos2d-x 支持 js腳本和lua腳本。在 ccConfig.h中這個值CC_ENABLE_SCRIPT_BINDING 有定義為 1

這里定義了一個靜態變量uObjectCount 這里用來記錄創建的對象數量的,當Ref的子類創建對象的時候,基類的Ref的構造函數里 uobjectCount就會自增1.這也使所有對象都有唯一的_ID值不會重復.。從這塊定義可以看到一個問題,這個函數並不是線程安全的,可以知道Cocos2d-x不適合多線程程序。

小結一下:構造函數里初始化了對象的 _ID 腳本 _luaID=0、並且初始化了引用計數 _referencecount為1。

看過了構造函數現在看析構函數

Ref::~Ref()
{
#if CC_ENABLE_SCRIPT_BINDING
    // if the object is referenced by Lua engine, remove it
    if (_luaID)
    {
        ScriptEngineManager::getInstance()->getScriptEngine()->removeScriptObjectByObject(this);
    }
    else
    {
        ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
        if (pEngine != NULL && pEngine->getScriptType() == kScriptTypeJavascript)
        {
            pEngine->removeScriptObjectByObject(this);
        }
    }
#endif
}

這個函數沒什么太特別的,又新出來幾個類ScriptEngineProtocol, ScriptEngineManager 從命名上理解,這兩個類應該是腳本管理器

總結一下析構當這個對象被Lua腳本引用時,在銷毀的時候通知腳本管理器把這個對象消除掉

當這個對象被其它腳本管理器引用是 這里尤其指 kScriptTypeJavascript 這個類型的腳本 js腳本。在這個對象銷毀時通知 js管理器清除這個對象。

看代碼切忌看到什么都跟進,那樣就跳到深坑出不來了。這塊我們就分析到這種程序,具體ScriptEngineProtocol, ScriptEngineManager 是什么玩意,分析腳本的時候我們再去看,這里只要知道在對象銷毀的時候,會去通知腳本管理器消除對象就可以了。

 

到現在為止Ref的對象創建和銷毀我們都了解了,下面看下引用 計數的操作。

先看 retain方法

void Ref::retain()
{
    CCASSERT(_referenceCount > 0, "reference count should greater than 0");
    ++_referenceCount;
}

retain的實現和想象中的沒什么差別,就是增加了一次引用計數。

再看 release方法

void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should 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
        delete this;
    }
}

代碼這么一大段子,不要慌張,其實這個函數一點也不復雜,中間一大段的注釋是告訴大家怎么樣使用Cocos2d-x引用計數機制的。

分析代碼,其實這個函數就干了兩件事

  1. 當調用release方法時,先減少一次此對象的引用計數
  2. 當引用計數referenceCount值為0的時候表明其它地方不再需要這個對象了,那么就 delete this 銷毀這個對象

中間那段含義是 在debug模式下如果引用計數已經為0並且這個對象還在自動釋放池里面,報一個警告。

這里我們先不看那段說明引用機制用法的注釋,那段注釋最后再看。

release方法看過了,我們再看一下autorelease方法

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

autorelease方法更簡單,這里出現了PoolManager這個類,從字面含義上看,自動釋放就是將當前對象加到了對象列表管理器的一個對象列表里面,特別注意到這個函數里面並沒有減少引用計數的操作

還有一個成員函數比較簡單,就是返回當前對象的引用次數的值

unsigned int Ref::getReferenceCount() const
{
    return _referenceCount;
}

這個函數沒什么可分析的,就是一個get方法。

分析到這里,引用機制有了進一步了解,小結一下有以下幾點:

  1. Ref對象要在子類里面創建,不能直接實例化Ref
  2. Ref創建后的引用計數為1
  3. 使用retain來增加1 次對這個對象的引用 計數
  4. 調用 release時會減少一次對象的引用 計數,當引用計數為0的時候就會銷毀這個對象。
  5. 用到了一個PoolManager對象列表管理器來管理Ref對象
  6. autorelease是通過內存管理器來釋放對象的,並且對象調用 autorelease函數時並沒有減少引用計數值

標紅的5,6兩點到目前我們只知道個大概,並不知道里面的具體機制是什么,所以下面跟小魚來分析一下這個PoolManager類,看看這家伙到底干了些什么

PoolManager類在CCAutoreleasePool.h文件中定義的

這個文件里面定義了兩個類

 

class CC_DLL AutoreleasePool

 

class CC_DLL PoolManager

 

Cocos2d-x的命名真是太好了,很大的程度上可以幫助我們來閱讀代碼,這里贊一個。

顧名思義

AutoreleasePool 是用來描述一個自動釋放對象池結構的定義

PoolManager 是用來管理所有的AutoreleasePool的管理器。結構很像我們經常用到過的 線程定義與線程管理器定義、socket連接的定義與socket連接管理器的定義,新手讀者可以學習這樣的數據結構用到自己的程序中。

下面我們先看 AutoreleasePool類的定義

先看一下AutoreleasePool的成員變量

std::vector<Ref*> _managedObjectArray;
    std::string _name;
    
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    /**
     *  The flag for checking whether the pool is doing `clear` operation.
     */
    bool _isClearing;
#endif

_managedObjectArray; 是用來保存這個自動釋放池里面加入的所有Ref對象

std::string _name;  可以了解到每個自動釋放池可以有一個名字,這個也方便我們查看和管理

bool _isClearing;  在debug模式下,用來標記當前這個對象列表是否已經做了清理操作。

通過這三個成員變量可以得知,這個類主要是操作_managedObjectArray這個vector來管理這個對象列表的所有對象,下面我們一個一個方法來分析,探究對象列表都有哪些管理操作。

從構造函數和析構函數開始

構造函數

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

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);
}

AutoreleasePool有兩個構造函數

第一個沒有參數的,做了兩件事情,1. 將對象列表的容量擴展到了 150個對象。 2. 將當前的對象列表加入到了對象列表管理器的隊列中

第二 個是帶一個參數的構造函數,可以給要創建的對象列表起一個名字,其它的操作與第一個默認構造函數一樣。

再看析構函數

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

析構函數也很簡單,也是干了兩件事

1. 執行了一次clear()方法

2. 將這個對象列表從自動釋放對象列表管理器里面刪除。  (疑惑1,調用 了 PoolManager::getInstance()->pop();方法,那么內存管理器里肯定有很多個對象列表,它怎么知道pop的一定是當前這個列表呢?這里面是不是有錯誤呢?這里究竟是怎么回事?后面我們看看能不能解決這個疑問)

 

接下來,我們來看一下,將Ref對象加入到對象列表的方法

 

void AutoreleasePool::addObject(Ref* object)
{
    _managedObjectArray.push_back(object);
}

這個函數很簡單,就是將Ref*對象加到_managedObjectArray的最后面。

 

再看一下clear方法

 

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = true;
#endif
    for (const auto &obj : _managedObjectArray)
    {
        obj->release();
    }
    _managedObjectArray.clear();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = false;
#endif
}

這個函數主要還是干了兩件事情

  1. 遍歷對象列表里面的每一個Ref對象,調用 它的release方法,這塊要注意,調用了release並不代表就徹底銷毀了這個對象,我們上面分析過release方法會減少對象的引用計數,只會銷毀引用計數為0的對象。
  2. 將所有對象的指針從對象列表里面清除。

這里面我們注意了,在debug模式下,會有一個_isClearing的狀態操作,這個有點類似線程同步的數據鎖的編程技巧,因為_managedObjectArray可能里面的對象很多,所以用這個_isClearing來將清除時的操作狀態鎖定,其它線程訪問這個對象列表的時候,會根據這個對象列表的狀態來決定是否立即引用這個對象列表里面的對象。

 

AutoreleasePool還提供了兩個工具方法

bool AutoreleasePool::contains(Ref* object) const
{
    for (const auto& obj : _managedObjectArray)
    {
        if (obj == object)
            return true;
    }
    return false;
}

這個方法是用來檢查指定的對象 object 是否在當前對象列表里面,返回 bool類型

void AutoreleasePool::dump()
{
    CCLOG("autorelease pool: %s, number of managed object %d\n", _name.c_str(), static_cast<int>(_managedObjectArray.size()));
    CCLOG("%20s%20s%20s", "Object pointer", "Object id", "reference count");
    for (const auto &obj : _managedObjectArray)
    {
        CC_UNUSED_PARAM(obj);
        CCLOG("%20p%20u\n", obj, obj->getReferenceCount());
    }
}

一看就知道 dump是個調試時用的方法,打印出當前對象列表的信息,包括名稱,裝載對象數量及每個對象的引用次數。游戲開發做性能分析及優化的時候肯定少不了用到這個函數。

到此我們看完了AutoreleasePool這個類的源碼,我給這個類起的名字是,對象自動釋放列表,不知道是否專業和恰當,希望眾多讀者指正。

小結一下

  1. AutoreleasePool 類有一個保存Ref對象的數組
  2. 每個AutoreleasePool可以有一個名字
  3. addObject  是 向對象列表加入一個Ref對象
  4. clear函數是 對AutoreleasePool中管理的每個Ref對象執行了一次release操作,並不一定會銷毀Ref 對象,要看Ref的引用 計數是否到達了0
  5. contains 是查詢指定的Ref對象是否在當前這個列表里面。
  6. dump 調試的函數,打印出對象列表里的對象狀態和以對象列表信息。

這里面我們有一個疑惑就是在clear方法里面只是調用了 PoolManager的pop方法,而沒有具體告訴PoolManager要消除哪個對象列表,這里面會不會出錯。帶着疑問我們來看一下PoolManager這個類

老方法,先看看PoolManager的成員變量

static PoolManager* s_singleInstance;
    
    std::deque<AutoreleasePool*> _releasePoolStack;
    AutoreleasePool *_curReleasePool;

s_singleInstance 用來實現單例的PoolManager對象

releasePoolStack 這是一個雙向隊列,來保存所有的 AutoreleasePool

AutoreleasePool *_curReleasePool; 從命名上看是當前的autoreleasePool ,難道這個對象和我們的疑問有關嗎?好像有點眉目了,繼續看方法。

構造函數 空的,沒啥可看的了,

析構函數

 

PoolManager::~PoolManager()
{
    CCLOGINFO("deallocing PoolManager: %p", this);
    
    while (!_releasePoolStack.empty())
    {
        AutoreleasePool* pool = _releasePoolStack.back();
        _releasePoolStack.pop_back();
        
        delete pool;
    }
}

析構函數干了一件事 遍歷所有對象列表 從 _releasePoolStack里面出棧,並銷毀這個對象列表。上面我們分析autoreleaesPool的時候知道 析構的時候會調用 clear方法,對每個對象進行引用計數及銷毀操作。這里面我們注意到releasePoolStack是以棧的行為來使用的。

看到了棧那么我們就來看PoolManager的壓棧和出棧方法。
壓棧:

void PoolManager::push(AutoreleasePool *pool)
{
    _releasePoolStack.push_back(pool);
    _curReleasePool = pool;
}
  1. 將一個AutoreleasePool對象指針放到_releasePoolStack的末尾(棧頂)
  2. 將當前操作的的對象池指向新壓入棧的pool

通過這里的代碼我們可以判斷,PoolManager同時只處理一個自動釋放對象池,也就是在棧頂的那一個,那么上面我們的疑問,基本可以解決了,因為Pop的永遠是_curReleasePool這個對象,也就是棧頂的一個元素。

 

出棧:

void PoolManager::pop()
{
    // Can not pop the pool that created by engine
    CC_ASSERT(_releasePoolStack.size() >= 1);
    
    _releasePoolStack.pop_back();
    
    // Should update _curReleasePool if a temple pool is released
    if (_releasePoolStack.size() > 1)
    {
        _curReleasePool = _releasePoolStack.back();
    }
}

干了兩件事

  1. 彈出棧頂的一個對象池
  2. 如果棧不為空將當前處理的對象池指向新的棧頂對象。

疑問2: _curReleasePool = _releasePoolStack.back(); 的條件是 _releasePoolStack.size() > 1 而不是 >= 1 這是為什么呢?如果releasePoolStack.size()==1 那么 curReleasePool 沒有得到重新賦值,這不出現了野指針了嗎?帶着疑問繼續找答案

這里有一個注釋我們可以解讀一下 不能彈出引擎自己創建的對象池。那么引擎自己創建的自動釋放對象池是哪個呢?

我們看一下單例方法來尋找一下線索

PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        s_singleInstance = new PoolManager();
        // Add the first auto release pool
        s_singleInstance->_curReleasePool = new AutoreleasePool("cocos2d autorelease pool");
        s_singleInstance->_releasePoolStack.push_back(s_singleInstance->_curReleasePool);
    }
    return s_singleInstance;
}

哈哈,果然在這里 單例方法創建PoolManager對象時還創建了一個 AutoreleasePool 對象 起的名字是 "cocos2d autorelease pool" 也就是說只要引擎啟動就會有一個默認的自動釋放對象列表,這個列表被放到了 PoolManager的棧底。

再仔細閱讀一下,可以注意到實際上首次調用這個函數得到單例的時候是放到了棧里面兩個AutoreleasePool 第一個是 AutoreleasePool new構造函數的時候放進去的第二個是在s_singleInstance->_releasePoolStack.push_back(s_singleInstance->_curReleasePool); 放進去的。因為這里面有兩個AutoreleasePool對象,所以疑問2就迎刃而解了。

再看看剩下的幾個函數。

void PoolManager::destroyInstance()
{
    delete s_singleInstance;
    s_singleInstance = nullptr;
}

銷毀PoolManager 這個函數很簡單標准的指針delete操作,沒什么可研究的。

 

AutoreleasePool* PoolManager::getCurrentPool() const
{
    return _curReleasePool;
}

_curReleasePool的get方法。

 

bool PoolManager::isObjectInPools(Ref* obj) const
{
    for (const auto& pool : _releasePoolStack)
    {
        if (pool->contains(obj))
            return true;
    }
    return false;
}

遍歷管理器里面所有的自動釋放對象隊列,尋找是否有obj對象,這里做的是指針的地址比較,返回bool類型。

回過頭我們再看看AutoreleasePool這個類的構造函數和析構函數

在構造函數中有這么一行代碼

PoolManager::getInstance()->push(this);

這說明,每當我們創建一個 釋放列表對象的時候,這個列表已經加入到了列表管理器里面此時的_curReleasePool指向了這個新創建的列表對象

在析構函數中還有一行代碼

PoolManager::getInstance()->pop();

在這個列表對象銷毀的時候,會將當前對象出棧操作。如果棧里面只剩下一個默認列表,那么_currReleasePool並不會重新指向它。

從AutoreleasePool的構造和析構來分析,我們可以推斷在使用cocos2d-x的內存管理的時候 不需要顯示的操作 列表管理器PoolManager的對象,只要重新創建一個AutoreleasePool對象就可以了。

我們再看一下AutoreleasePool構造函數前面有一段注釋。

/**
     * @warn Don't create an auto release pool in heap, create it in stack.
     * @js NA
     * @lua NA
     */
    AutoreleasePool();

終於理解了這段注釋的意思,不要在堆中創建 pool 要在棧中創建, 說白一點,就是要控制 AutoreleasePool的作用域,不要new這個對象。

結合上面沒有翻譯的那一大段Ref引用計數使用方法的注釋這里總結一下  Ref的引用計數 AutoreleasePool 及 PoolManager這三個類的工作方式及使用方法

  1. Ref必須被子類繼承來使用不能直接創建Ref的實例對象,因為它的構造函數是protected的訪問類型
  2. 當Ref的對象被創建的時候它的引用計數為1
  3. Ref::release調用時會立刻減少引用計數而 Ref::autorelease 不會立刻減少引用計數,它將Ref對象加入到當前的自動釋放對象池中去統一的時機來進行釋放,疑問3:到底arutrelease在什么時候被引擎調用的呢?
  4. Ref對象創建后可以調用一次release 或 autorelease來釋放,若多次引用那么要 retain 和 release/autorelease 成對的出現,否則就會出現引用次數不正確的情況,在dubug模式下,release是有引用次數檢測的,會打印出assert信息。
  5. 不需要我們來操作PoolManager這個單例對象,PoolManager是引擎內部來管理的
  6. 引擎初始化后就會創建一個默認的自動釋放對象列表並加入到了PoolManager里面進行管理,這個默認對象列表是不會被自動機制清除的,只 有在析構的時候才能銷毀。
  7. autoreleasePool要在棧上創建,也就是要使用局部變量創建。不要new來創建。
  8. 還要注意一點在引擎初始化的時候實際上默認的有兩個cocos2d autorelease pool 對象列表 這也是為什么 PoolManager::Pop方法里面為什么_currReleasePool不會出現野指針的關鍵所在。
    在這里小魚想給cocos2d-x這塊的代碼優化一下,這里各位讀者也一起提點見意
PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        s_singleInstance = new PoolManager();
        // Add the first auto release pool
        s_singleInstance->_curReleasePool = new AutoreleasePool("cocos2d autorelease pool");
        //s_singleInstance->_releasePoolStack.push_back(s_singleInstance->_curReleasePool);
    }
    return s_singleInstance;
}
void PoolManager::pop()
{
    // Can not pop the pool that created by engine
    CC_ASSERT(_releasePoolStack.size() >= 1);
    CC_ASSERT(_releasePoolStack.size() > 1 );     
    _releasePoolStack.pop_back();
    
    // Should update _curReleasePool if a temple pool is released
    if (_releasePoolStack.size() > 0) //這里改成0
    {
        _curReleasePool = _releasePoolStack.back();
    }
}

帶着疑問3 我們來思考一下,當定義一個局部的autoreleasePool變量時

{

   AutoreleasePool pool1;
   ………………

}

這個pool1的作用域只在這對花括號中間,此時PoolManager里面的currReleasePool就是這個pool1,當程序執行完花括號的內容后 pool1的作用域到期,調用了pool1的析構函數 再回憶一下 AutoreleasePool的析構函數

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

調用了clear 在clear里面對所有在管理列表的Ref 對象調用了release減少了一次引用計數,並銷毀那些引用計數為0的對象。

調用了PoolManager::pop方法, 調用這個方法后將當前的這個內存對象列表從管理器里面刪除掉。

這樣在這個局部作用域之間所有的內存管理實際上是交給了pool1來完成的,真是好方法,使用簡單方便。

那么默認的自動釋放管理是在哪里調用 的呢? 回顧一下上一章節,我們在看Director類的時候在主循環里面有這樣一行代碼。

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (! _invalid)
    {
        drawScene();
     
        // release the objects
        
PoolManager::getInstance()->getCurrentPool()->
clear();
    }
}
看到標紅的那行代碼,多讓人興奮,在每次主循環的最后都會主動的去釋放一次自動管理的對象。
到此疑問3也解決了,臨時的自動釋放對象池在對象池的作用域內有效,引擎默認的自動釋放對象池是在每一次邏輯幀的最后被調用和釋放的。
現在,Ref、AutoreleasePool、PoolManager這三個類的源碼我們已經分析完成了,並且針對 PoolManager類還提出了一個小小的優化方案。
回過本章節的開始,我們留了一個類放到最后來閱讀一下。那就是Clonable
class CC_DLL Clonable
{
public:
    /** returns a copy of the Ref */
    virtual Clonable* clone() const = 0;
    /**
     * @js NA
     * @lua NA
     */
    virtual ~Clonable() {};

    /** returns a copy of the Ref.
     @deprecated Use clone() instead
     */
    CC_DEPRECATED_ATTRIBUTE Ref* copy() const
    {
        // use "clone" instead
        CC_ASSERT(false);
        return nullptr;
    }
};

 

可以看到這個類是一個抽象類,提供了一個抽象方法  virtual Clonable* clone() const = 0;  顯而易見這是讓我們在想提供clone方法(對象復制方法)的類統一繼續這個父類,不同的繼承類要實現自己特有的clone方法

我在工程中隨便搜索了一下,看看有沒有具體用到clone方法的類,果然找到了一些,這里貼一個cocos2d-x中的一個類的代碼從使用中來學習 Clonable這個類和總結一下今天所啰嗦的這點事。

class CC_DLL __Bool : public Ref, public Clonable
{
public:
    __Bool(bool v)
        : _value(v) {}
    bool getValue() const {return _value;}

    static __Bool* create(bool v)
    {
        __Bool* pRet = new __Bool(v);
        if (pRet)
        {
            pRet->autorelease();
        }
        return pRet;
    }

    /* override functions */
    virtual void acceptVisitor(DataVisitor &visitor) { visitor.visit(this); }

    __Bool* clone() const
    {
        return __Bool::create(_value);
    }
private:
    bool _value;
};

這是一個bool數據類型的定義(我虎軀一震,連數據類型都有定義啊,果然這個引擎很強大)

可以看到 這個bool類型繼承了 Ref(這是要放到內存管理器里面統一管理啊,並且在這個bool類型創建的時候就已經使用到了autorelease來做內存釋放,學到啦哈哈)

這個類還繼承了 Clonable這個類,實現 了clone函數 ,實現的很簡單就是重新創建了一個與當前值相同的對象,返回了新的對象引用。

哈哈,我越發我舉的這個類太恰當了,又有內存管理的應用,又有clone方法的使用。

同理其它cocos2d-x的數據類型大多也都是這種創建模式,不要直接去new對象,提供一個靜態方法create來創建對象,並且新創建的對象直接加入到自動的內存管理里面,來實現自動釋放,這樣避免了你到處去想着delete。安全方便。

 

本章節就到這里, 下一章節,我們看一下出場率很高的 Node 類。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM