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
};
我們來一根一根看看這個金掃帚有什麼強大的功能吧。
成員變量
- std::vector<Ref*> _managedObjectArray //存放所有對象的內存池
- std::string _name //這個變量提示我們每個內存池其實是有自己的名字的
- 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(),也就是說遊戲的主循環每執行一次,就會清除一次垃圾資源。