cocos2d-x源碼剖析-3-貼身女僕PoolManager

PoolManager

上一節我們說Cocos2d是一顆大樹的話,Ref類就是這棵大樹的根,那麼爲了這顆大樹的健康我們需要給它澆水除蟲,在程序中,這些繁重的工作都要交給貼身女僕PoolManager去做。

PoolManager是Cocos2d最最要的內存池管理類,它管理着AutoreleasePool內存池,它可以將垃圾資源釋放,提高整個程序的性能。

class CC_DLL PoolManager
{
public:

    CC_DEPRECATED_ATTRIBUTE static PoolManager* sharedPoolManager() { return getInstance(); }
    static PoolManager* getInstance();
    
    CC_DEPRECATED_ATTRIBUTE static void purgePoolManager() { destroyInstance(); }
    static void destroyInstance();
   
    AutoreleasePool *getCurrentPool() const;

    bool isObjectInPools(Ref* obj) const;


    friend class AutoreleasePool;
    
private:
    PoolManager();
    ~PoolManager();
    
    void push(AutoreleasePool *pool);
    void pop();
    
    static PoolManager* s_singleInstance;
    
    std::vector<AutoreleasePool*> _releasePoolStack;
};

看到程序中有pop,push操作我們可以大概看出這是一個棧數據結構。將構造函數和析構函數聲明爲私有,並且定義了getInstance()函數可以看出這是一個單例函數,也就是說全局只有一個實例對象。

 

std::vector<AutoreleasePool*> _releasePoolStack

可以看出,內存池是一個AutoreleasePool指針類型的鏈表,我們可以看看pop,push操作

pop

void PoolManager::pop()
{
    CC_ASSERT(!_releasePoolStack.empty());
    _releasePoolStack.pop_back();
}

push

void PoolManager::push(AutoreleasePool *pool)
{
    _releasePoolStack.push_back(pool);
}

pop,posh操作只是封裝了vector函數的pop_back和push_back函數。

 

PoolManager()

PoolManager::PoolManager()
{
    _releasePoolStack.reserve(10);
}

內存池管理類一開始給出了10個空間。

 

 ~PoolManager()

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

 ~PoolManager()不只釋放了自己,還將它管理的所有內存池全部釋放了。

 

destroyInstance()

銷燬自身,釋放內存,purgePoolManager()和此函數用法相同。因爲purgePoolManager()內部只調用了destroyInstance。

purgePoolManager:

CC_DEPRECATED_ATTRIBUTE static void purgePoolManager() { destroyInstance(); }

 

getCurrentPool()

獲得當前內存池,由於是棧結構所以只需要返回棧的最後一位即可。

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

 

isObjectInPools()

判斷某個對象現在是否存在內存池中。

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

由於PoolManager存放了所有的AutoreleasePool對象,所以只要遍歷PoolManager內的內存池,並判斷這些內存池是否含有傳入的對象就行了,pool->contains(obj)中的contains()是AutoreleasePool的一個成員函數,它用來判斷內存池中是否包含傳入的對象。

 

小結

就這?看完上面的代碼以後,你一定有這樣的疑惑,我的回答是:就這!貼心女僕只有這些功能,手裏掌管着所有內存池的權限,可以任意操控他們,當然想讓PoolManager女僕發揮最大的威力,你得把她的掃帚給她啊。

 

AutoreleasePool ——女僕的金掃帚

class CC_DLL AutoreleasePool
{
public:

    AutoreleasePool();

    AutoreleasePool(const std::string &name);
    
    ~AutoreleasePool();

    void addObject(Ref *object);

    void clear();
    
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    bool isClearing() const { return _isClearing; };
#endif
    
    bool contains(Ref* object) const;

    void dump();
    
private:
    std::vector<Ref*> _managedObjectArray;
    std::string _name;
    
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    bool _isClearing;
#endif
};

 我們來一根一根看看這個金掃帚有什麼強大的功能吧。

 

成員變量

  1.  std::vector<Ref*> _managedObjectArray  //存放所有對象的內存池
  2.  std::string _name //這個變量提示我們每個內存池其實是有自己的名字的
  3. bool _isClearing //判斷內存池有沒有被清空

 

AutoreleasePool()

AutoreleasePool()用來初始化內存池,初始化150個空間,並將自己添加到PoolManager的棧中,同時設置_isClearing爲false,指定_name爲空字符串,AutoreleasePool()有一個重載,它含有std::string參數,用來指定初始化_name的值。

AutoreleasePool():

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

AutoreleasePool(const std::string &name):

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

 

clear()

釋放內存池中的資源。

我們先看看老版本怎麼寫的:

void AutoreleasePool::clear()
{
    for (const auto &obj : _managedObjectArray)
    {
        obj->release();
    }
    _managedObjectArray.clear();
}

在老版本中會先遍歷整個數組,讓每一個對象都release一次,然後再調用clear()函數清除數組。他的作用在哪裏呢,我們知道在引用計數中release會讓引用計數減一,如果引用計數減小到0的時候,它將被釋放,而AutoreleasePool::clear()函數使得每一個對象都release一次,它的作用就在於,把你創建了但是沒有使用的垃圾內存釋放,這樣就起到了清除垃圾的效果,但是,這個代碼再仔細看看,寫的也太糟糕了,雖然使用release釋放了Ref對象的垃圾,但是自身卻使用了vector::clear()函數來清除數組,我們知道vector調用clear之後, vector的尺寸(size)將變成0. 但它的容量卻並不發生變化, vector本身並不釋放任何內存。這樣一來又增加自己產生的垃圾了。不過在最新版本中已經將這個糟糕的寫法的改進了。

船新版本:

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

std::vector<Ref*> releasings創建了一個空vector,然後releasings.swap(_managedObjectArray)交換兩個vector的內容,這樣內存池就被清空了,那麼垃圾如何釋放呢?這時新建的releasings就會得到原來內存池的內容,再使用releasings來釋放就行了。

for (const auto &obj : releasings)
    {
        obj->release();
    }

這樣在此代碼塊結束的時候releasings也就釋放了。

 

~AutoreleasePool()

~AutoreleasePool()中調用了clear函數,並且將當前內存池彈出棧。

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

 

contains()

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

用來判斷內存池中是否包含某個指定對象。

 

addObject()

向內存池中添加對象。

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

 

總結

我們可以使用PoolManager來得到當前的AutoreleasePool,讓後通過 AutoreleasePool的clear函數來清除垃圾內存。也就是女僕用掃帚掃地的意思。

在什麼時候掃地呢?

我們打開Director類的文件CCDirector.h,我們可以找到一個mainLoop函數,來看看他的實現:

void Director::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (_restartDirectorInNextLoop)
    {
        _restartDirectorInNextLoop = false;
        restartDirector();
    }
    else if (! _invalid)
    {
        drawScene();
     
        // 這裏釋放了垃圾資源
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

 也就是說每次mainLoop執行就會釋放一次資源,還記得main.c裏面的main函數return了一個特殊的東西嗎?

return Application::getInstance()->run();

這個run就是遊戲的主循環,在主循環裏使用了director->mainLoop(),也就是說遊戲的主循環每執行一次,就會清除一次垃圾資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章