在这里要先明白cocos2d-x内存管理的来源。因为cocos2d-x是将cocos2d重写了一遍,内存管理也是和cocos2d一样,通过引用计数来控制。其实现是在cocos2d-x的基类CCObject中,所以要弄懂cocos2d-x的内存管理,一定会跟CCObject类打交道。所以可以从CCObject类下手。
class CC_DLL CCObject : public CCCopying
{
public:
// object id, CCScriptSupport need public m_uID
//与脚本相关,略过
unsigned int m_uID;
// Lua reference id
//与脚本相关,略过
int m_nLuaID;
protected:
// count of references
//引用计数
unsigned int m_uReference;
// count of autorelease
//在内存回收池的引用次数,也就是调用一次autorelease这个值加1
unsigned int m_uAutoReleaseCount;
public:
//构造
CCObject(void);
/**
* @lua NA
*/
//析构
virtual ~CCObject(void);
//引用计数减一,为0回收内存
void release(void);
//引用计数加1
void retain(void);
//把当前对象加入到内存回收池中
CCObject* autorelease(void);
//拷贝
CCObject* copy(void);
//是否单引用
bool isSingleReference(void) const;
//返回当前的引用计数
unsigned int retainCount(void) const;
//是否是同一个对象
virtual bool isEqual(const CCObject* pObject);
//实现观察者观察CCObject对象
virtual void acceptVisitor(CCDataVisitor &visitor);
//更新
virtual void update(float dt) {CC_UNUSED_PARAM(dt);};
//声明CCAutoreleasePool为友元类
friend class CCAutoreleasePool;
};
再看其实现:
CCObject::CCObject(void)
: m_nLuaID(0)
, m_uReference(1) // 当对象被创建时,应用计数初始化为1,这样便能保证该对象至少能存活一帧
, m_uAutoReleaseCount(0)
{
static unsigned int uObjectCount = 0;
m_uID = ++uObjectCount;//与脚本相关,略过
}
CCObject::~CCObject(void)
{
// if the object is managed, we should remove it
// from pool manager
if (m_uAutoReleaseCount > 0)//如果当该对象被销毁时,仍存在于内存回收池中,那么内存回收池清掉该对象
{
CCPoolManager::sharedPoolManager()->removeObject(this);
}
// if the object is referenced by Lua engine, remove it
//下面与脚本引擎相关,略过
if (m_nLuaID)
{
CCScriptEngineManager::sharedManager()->getScriptEngine()->removeScriptObjectByCCObject(this);
}
else
{
CCScriptEngineProtocol* pEngine = CCScriptEngineManager::sharedManager()->getScriptEngine();
if (pEngine != NULL && pEngine->getScriptType() == kScriptTypeJavascript)
{
pEngine->removeScriptObjectByCCObject(this);
}
}
}
CCObject* CCObject::copy()
{
//拷贝,空实现
return copyWithZone(0);
}
void CCObject::release(void)
{
CCAssert(m_uReference > 0, "reference count should greater than 0");
//引用计数减1
--m_uReference;
if (m_uReference == 0)//如果引用计数为0,则删除该对象
{
delete this;
}
}
void CCObject::retain(void)
{
CCAssert(m_uReference > 0, "reference count should greater than 0");
//引用计数加1
++m_uReference;
}
CCObject* CCObject::autorelease(void)
{
//加入到自动回收池中
CCPoolManager::sharedPoolManager()->addObject(this);
return this;
}
bool CCObject::isSingleReference(void) const
{
//若果引用计数为1,则表明当前对象是单引用
return m_uReference == 1;
}
unsigned int CCObject::retainCount(void) const
{
//返回引用计数
return m_uReference;
}
bool CCObject::isEqual(const CCObject *pObject)
{
//判断是否是同一个对象
return this == pObject;
}
void CCObject::acceptVisitor(CCDataVisitor &visitor)
{
//调用观察者的visitObject观察该对象
visitor.visitObject(this);
}
再看下CCAutoreleasePool的头文件,里面有两个类
//内存回收池类
class CC_DLL CCAutoreleasePool : public CCObject
{
//内存回收池中对象
CCArray* m_pManagedObjectArray;
public:
//构造
CCAutoreleasePool(void);
//析构
~CCAutoreleasePool(void);
//增加一个对象到回收池中
void addObject(CCObject *pObject);
//在回收池中移除一个对象
void removeObject(CCObject *pObject);
//清空回收池中的所有对象
void clear();
};
/**
* @js NA
* @lua NA
*/
//内存回收池管理类
class CC_DLL CCPoolManager
{
//存放回收池的栈
CCArray* m_pReleasePoolStack;
//内存回收池
CCAutoreleasePool* m_pCurReleasePool;
//获取当前的内存回收池
CCAutoreleasePool* getCurReleasePool();
public:
//构造
CCPoolManager();
//析构
~CCPoolManager();
//清空所管理的回收池中的所有对象
void finalize();
//把一个内存回收池压栈
void push();
//把一个内存回收池出栈
void pop();
//把一个对象从当前所管理的回收池中移除掉
void removeObject(CCObject* pObject);
//把一个对象加到当前所管理的回收池中
void addObject(CCObject* pObject);
//获取回收池的管理对象
static CCPoolManager* sharedPoolManager();
//释放回收池管理对象的资源
static void purgePoolManager();
//生命内存回收池类为友元类
friend class CCAutoreleasePool;
};
其对应的实现:
static CCPoolManager* s_pPoolManager = NULL;
CCAutoreleasePool::CCAutoreleasePool(void)
{
//在堆中创建一个用于存放对象的容器
m_pManagedObjectArray = new CCArray();
//初始化这个容器
m_pManagedObjectArray->init();
}
CCAutoreleasePool::~CCAutoreleasePool(void)
{
//当该内存回收池被销毁时同时释放掉存放回收池的栈
CC_SAFE_DELETE(m_pManagedObjectArray);
}
void CCAutoreleasePool::addObject(CCObject* pObject)
{
//把指定对象加入到栈中
m_pManagedObjectArray->addObject(pObject);
//引用计数至少大于1
CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");
//指明该对象是受回收池管理(因为CCAutoreleasePool是CCObject的友元类,所以可以直接访问CCObject类的保护属性)
++(pObject->m_uAutoReleaseCount);
//引用计数减1。(这里之所以要减1是因为在执行m_pManagedObjectArray->addObject(pObject)时pObject的引用计数也会加1,所以这里一定要调用release,将
//引用计数减1,这样才能保持pObject对象的引用计数与原始的一样)
pObject->release(); // no ref count, in this case autorelease pool added.
}
void CCAutoreleasePool::removeObject(CCObject* pObject)
{
//从内存回收池中移除该对象(这里之所以有个循环是因为当一个对象加入到内存回收池时m_uAutoReleaseCount会加1,再加一次时m_uAutoReleaseCount也会再次加1,所以这里要加个循环,保证对象的引用计数得以正确次数减1)
for (unsigned int i = 0; i < pObject->m_uAutoReleaseCount; ++i)
{
m_pManagedObjectArray->removeObject(pObject, false);
}
}
void CCAutoreleasePool::clear()
{
if(m_pManagedObjectArray->count() > 0)//如果内存回收池中有对象
{
//CCAutoreleasePool* pReleasePool;
#ifdef _DEBUG
int nIndex = m_pManagedObjectArray->count() - 1;
#endif
CCObject* pObj = NULL;
CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)//遍历内存回收池中的对象
{
if(!pObj)//存在空对象,跳出
break;
--(pObj->m_uAutoReleaseCount);//被回收池管理的次数减1
//(*it)->release();
//delete (*it);
#ifdef _DEBUG
nIndex--;
#endif
}
//移除所有回收池中的对象
m_pManagedObjectArray->removeAllObjects();
}
}
//--------------------------------------------------------------------
//
// CCPoolManager
//
//--------------------------------------------------------------------
CCPoolManager* CCPoolManager::sharedPoolManager()
{
//创建一个内存回收池的管理对象的单例
if (s_pPoolManager == NULL)
{
s_pPoolManager = new CCPoolManager();
}
return s_pPoolManager;
}
void CCPoolManager::purgePoolManager()
{
//释放内存回收池的管理对象所指向的内存资源
CC_SAFE_DELETE(s_pPoolManager);
}
CCPoolManager::CCPoolManager()
{
//创建一个用于存放回收池的容器
m_pReleasePoolStack = new CCArray();
m_pReleasePoolStack->init();
//初始化内存回收池
m_pCurReleasePool = 0;
}
CCPoolManager::~CCPoolManager()
{
//清空所管理的回收池中的所有对象
finalize();
// we only release the last autorelease pool here
//防止野指针
m_pCurReleasePool = 0;
//删除第一个回收池对象(既然可以有多个内存回收池存在,为什么不用removeAllObjects,或者又因为在每帧调用的时候可以保证管理器中始终保持只有一个回收池???)
m_pReleasePoolStack->removeObjectAtIndex(0);
//释放存放回收池的容器的资源
CC_SAFE_DELETE(m_pReleasePoolStack);
}
void CCPoolManager::finalize()
{
if(m_pReleasePoolStack->count() > 0)//回收池栈中存在回收池
{
//CCAutoreleasePool* pReleasePool;
CCObject* pObj = NULL;
CCARRAY_FOREACH(m_pReleasePoolStack, pObj)
{
if(!pObj)//存在空对象,跳出
break;
CCAutoreleasePool* pPool = (CCAutoreleasePool*)pObj;
pPool->clear();//清空当前的回收池中的所有对象
}
}
}
void CCPoolManager::push()
{
//创建一个内存回收池
CCAutoreleasePool* pPool = new CCAutoreleasePool(); //ref = 1
//把刚创建出来的回收池的地址赋给m_pCurReleasePool
m_pCurReleasePool = pPool;
//回收池入栈
m_pReleasePoolStack->addObject(pPool); //ref = 2
//抵消刚才调用addObject(pPool)时的引用计数加1
pPool->release(); //ref = 1
}
void CCPoolManager::pop()
{
if (! m_pCurReleasePool)//当前的内存回收池为空,则返回
{
return;
}
//获取当前管理器中回收池的个数
int nCount = m_pReleasePoolStack->count();
//清空当前的回收池的所有对象
m_pCurReleasePool->clear();
if(nCount > 1)//回收池个数大于1
{
m_pReleasePoolStack->removeObjectAtIndex(nCount-1);//移除最后一个回收池,模拟出栈操作
// if(nCount > 1)
// {
// m_pCurReleasePool = m_pReleasePoolStack->objectAtIndex(nCount - 2);
// return;
// }
m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount - 2);//指定当前的回收池为出栈后的最后一个回收池
}
/*m_pCurReleasePool = NULL;*/
}
void CCPoolManager::removeObject(CCObject* pObject)
{
//断言当前的回收池不能为空
CCAssert(m_pCurReleasePool, "current auto release pool should not be null");
//从当前的回收池中移除该对象
m_pCurReleasePool->removeObject(pObject);
}
void CCPoolManager::addObject(CCObject* pObject)
{
//将对象到内存回收池中
getCurReleasePool()->addObject(pObject);
}
有了以上的了解后便可以用几个例子来熟悉cocos2d-x的内存管理大概是怎样的。
定义三个测试类,然后在AppDelegate中启动TestScene
class TestScene : public CCLayer
{
public:
virtual bool init();
static cocos2d::CCScene* scene();
CREATE_FUNC(TestScene);
};
class TestLayer : public CCLayer {
public:
TestLayer() {
CCLog("TestLayer()");
};
~TestLayer() {
CCLog("~TestLayer()");
};
CREATE_FUNC(TestLayer);
virtual bool init() {
CCLog("TestLayer init");
return true;
};
};
class TestSp : public CCNode {
public:
TestSp() {
CCLog("TestSprite()");
};
~TestSp() {
CCLog("~TestSprite()");
};
CREATE_FUNC(TestSp);
virtual bool init() {
CCLog("TestSprite init");
return true;
};
};
在第一个例子中,可以看到cocos2d-x是可以自动释放掉无用的对象
CCScene* TestScene::scene()
{
CCScene * scene = NULL;
do
{
scene = CCScene::create();
CC_BREAK_IF(! scene);
TestScene *layer = TestScene::create();
CC_BREAK_IF(! layer);
scene->addChild(layer);
} while (0);
return scene;
}
bool TestScene::init()
{
bool bRet = false;
do
{
//创建一个测试对象
TestLayer* testLayer = TestLayer::create();
//执行下面的代码后便表示testLayer已被当前的场景所管理,不会自动释放,通俗的讲游戏的执行需要它,所以才把它add到场景中,受当前对象管理
this->addChild(testLayer);
//创建一个测试对象
TestSp* testSprite = TestSp::create();
//创建完testSprite测试对象后下面的代码并没有引用它,所以它被cocos2d-x引擎看做是无用的对象,是需要进行回收的
bRet = true;
} while (0);
return bRet;
}
打印的结果如下:
TestLayer()
TestLayer init
TestSprite()
TestSprite init
~TestSprite()
可以看到无用对象testSprite的析构函数确实被调用了。
接下来便分析这个调用过程和逻辑。
当testLayer和testSprite调用create时都调用了autorelease方法,其导致的结果是会把这两个对象都加到当前的一个内存回收池中。
CCObject* CCObject::autorelease(void)
{
//加入到自动回收池中
CCPoolManager::sharedPoolManager()->addObject(this);
return this;
}
void CCPoolManager::addObject(CCObject* pObject)
{
//将对象到内存回收池中
getCurReleasePool()->addObject(pObject);
}
在加入到回收池的时候会引起变量m_uAutoReleaseCount的变化,当大于0时说明它是受回收池管理的
void CCAutoreleasePool::addObject(CCObject* pObject)
{
//把指定对象加入到栈中
m_pManagedObjectArray->addObject(pObject);
//引用计数至少大于1
CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");
//指明该对象是受回收池管理(因为CCAutoreleasePool是CCObject的友元类,所以可以直接访问CCObject类的保护属性)
++(pObject->m_uAutoReleaseCount);
//引用计数减1。(这里之所以要减1是因为在执行m_pManagedObjectArray->addObject(pObject)时pObject的引用计数也会加1,所以这里一定要调用release,将
//引用计数减1,这样才能保持pObject对象的引用计数与原始的一样)
pObject->release(); // no ref count, in this case autorelease pool added.
}
这个时候testLayer和testSprite的引用计数的变化为:
1)构造对象的时候时候由于都是CCObject的子类的所以引用计数都为1
2)当调用了autorelease后会加入到内存回收池中,这时引用计数会加1,然后又被release了减1,所以最后的引用计数还是1。
3)因为testLayer又被addChild到场景中,所以引用计数又加1,即testLayer的引用计数为2,而testSprite的引用计数为1。
4)前面的文章提过在主循环中,主要处理两件事,一个场景的渲染,另一个就是内存的管理,也就是说,cocos2d-x游戏中,每一帧都会处理一次内存管理。
void CCDisplayLinkDirector::mainLoop(void)
{
if (m_bPurgeDirecotorInNextLoop)
{
m_bPurgeDirecotorInNextLoop = false;
purgeDirector();
}
else if (! m_bInvalid)
{
drawScene();
// release the objects
//释放内存
CCPoolManager::sharedPoolManager()->pop();
}
}
可以看到管理的内存是调用时CCPoolManager::pop。
void CCPoolManager::pop()
{
if (! m_pCurReleasePool)//当前的内存回收池为空,则返回
{
return;
}
//获取当前管理器中回收池的个数
int nCount = m_pReleasePoolStack->count();
//清空当前的回收池的所有对象
m_pCurReleasePool->clear();
if(nCount > 1)//回收池个数大于1
{
m_pReleasePoolStack->removeObjectAtIndex(nCount-1);//移除最后一个回收池,模拟出栈操作
// if(nCount > 1)
// {
// m_pCurReleasePool = m_pReleasePoolStack->objectAtIndex(nCount - 2);
// return;
// }
m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount - 2);//指定当前的回收池为出栈后的最后一个回收池
}
/*m_pCurReleasePool = NULL;*/
}
可以看到这时当前的内存回收池会清空掉其所有的对象。
void CCAutoreleasePool::clear()
{
if(m_pManagedObjectArray->count() > 0)//如果内存回收池中有对象
{
//CCAutoreleasePool* pReleasePool;
#ifdef _DEBUG
int nIndex = m_pManagedObjectArray->count() - 1;
#endif
CCObject* pObj = NULL;
CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)//遍历内存回收池中的对象
{
if(!pObj)//存在空对象,跳出
break;
--(pObj->m_uAutoReleaseCount);//被回收池管理的次数减1
//(*it)->release();
//delete (*it);
#ifdef _DEBUG
nIndex--;
#endif
}
//移除所有回收池中的对象
m_pManagedObjectArray->removeAllObjects();
}
}
执行完这个操作后,当前的内存回收池会移除其所有对象(removeAllObjects会使引用计数减1)。所以这时testLayer的引用计数为1,而testSprite的引用计数为0。
void CCArray::removeAllObjects()
{
ccArrayRemoveAllObjects(data);
}
void ccArrayRemoveAllObjects(ccArray *arr)
{
while( arr->num > 0 )
{
(arr->arr[--arr->num])->release();
}
}
5)当调用release时会检测引用计数是否为0
void CCObject::release(void)
{
CCAssert(m_uReference > 0, "reference count should greater than 0");
//引用计数减1
--m_uReference;
if (m_uReference == 0)//如果引用计数为0,则删除该对象
{
delete this;
}
}
所以由于testSprite的引用计数为0,从而调用了testSprite的析构函数,而testLayer的引用计数为1,所以没有调用testLayer的析构函数
6)执行完以上操作由于之前管理的testSprite和testLayer的回收池出栈,导致存活下来的testLayer是不受内存回收池管理的,这时它的m_uAutoReleaseCount是为0的(这个变量的作用是在CCObject的析构函数中
CCObject::~CCObject(void)
{
// if the object is managed, we should remove it
// from pool manager
if (m_uAutoReleaseCount > 0)//如果当该对象被销毁时,仍存在于内存回收池中,那么内存回收池清掉该对象
{
CCPoolManager::sharedPoolManager()->removeObject(this);
}
// if the object is referenced by Lua engine, remove it
//下面与脚本引擎相关,略过
if (m_nLuaID)
{
CCScriptEngineManager::sharedManager()->getScriptEngine()->removeScriptObjectByCCObject(this);
}
else
{
CCScriptEngineProtocol* pEngine = CCScriptEngineManager::sharedManager()->getScriptEngine();
if (pEngine != NULL && pEngine->getScriptType() == kScriptTypeJavascript)
{
pEngine->removeScriptObjectByCCObject(this);
}
}
}
这样便能保证该对象如果被手动delete掉,且是受内存回收池管理的,那么内存回收池也会将其清掉)。
7)最后testLayer的资源便全权由场景来释放。所以总结出来的一句话是谁管理,谁负责释放(最后testLayer是由它的父类管理,所以父类有责任决定什么时候去释放它)。