cocos2d-x的内存管理机制是回收池和引用计数合用来进行管理,这套机制一直为人们所津津乐道。
C++并没有内存管理的机制,都是用new/delete来进行对象的管理(STL和boost倒是有智能指针)
现在让我来介绍 一下cocos2d-x的内存管理机制。
cocos2d-x的内存管理机制用到的类有CCObject, CCAutoreleasePool, CCPoolManager这几个类
CCCopying其实是个接口,里面并没有任何实现。这里说一下宏命令CC_UNUESD_PARAM(),查看他的定义,很容易发现
#define CC_UNUSED_PARAM(unusedparam) (void)unusedparam
就是说这个宏完全没有执行任何命令,这样写的原因主要是历史遗留原因,ojb-c不存在纯虚函数并且传入参数不使用编译器会发出警告,这样用一个宏既可以防止警告也有一定的解释作用
CCCopying并没有父类,这样说来CCObject算是继承树上比较高的结点了,它负责了内存管理部分的引用计数
可以看到,构造函数把引用数置为1,retain会把引用数加1,相反release会减1,每次release之后检测引用数,为0的话会析构当前对象。
另外cocos2d-x封装了一些容器,CCArray和CCDictionnary等,这些都是CCObject的容器,每当这些容器增加元素时,会调用一次retain,移除时会进行release
我们可以来对比一下下面的代码看一下引用计数和C++的new/delete的区别和优点
通过对比很容易发现两者不一样,通过引用计数,release和delete不同,release并未等于析构,当所有对象都不持有该对象的指针时,才析构该对象,下面,我们再看看CCObject的另一个关于内存管理的函数,autorealease
autorelease并未对引用进行任何的操作,只见他放进了CCPoolManager里面,这个其实就是内存池的管理者,那么我们先来看一下内存池是怎样的
内存池其实就是一个CCArray的封装,代码很简单,这里就不贴源文件了,不过这里要注意几点
1.CCAutorelease是CCObject的子类,所以也是要进行引用计数等的管理的
2.尽管addObject和removeObject是向一个CCArray添加对象,可是内存池进行了一些操作,这两个操作并不会改变对象的引用数,也就是说,加入内存池并不改变对象的引用数
CCPoolManager就是一个CCAutorelease的栈,每次pop都把栈顶的内存池进行处理,把栈顶内存池管理的元素release掉。至于pop在什么时候调用?看过我写的 主流程(mainloop)分析可以知道——
CCDiretor在mainloop()中调用了drawScene()和CCPoolManager::sharedPoolManager()->pop()
也就是说在每帧结束后,就会进行内存的管理
总结来说,cocos2d-x内存管理的引用计数部分通过retain/release来进行计数增减,而内存池管理就通过autorelease来进行管理,在每帧结束时自动回收
C++并没有内存管理的机制,都是用new/delete来进行对象的管理(STL和boost倒是有智能指针)
现在让我来介绍 一下cocos2d-x的内存管理机制。
cocos2d-x的内存管理机制用到的类有CCObject, CCAutoreleasePool, CCPoolManager这几个类
现在先讲CCObject,CCOjbect继承于CCCopying
//CCObject.h
class CC_DLL CCCopying
{
public:
virtual CCObject* copyWithZone(CCZone* pZone);
};
//CCObject.cpp
CCObject* CCCopying::copyWithZone(CCZone *pZone)
{
CC_UNUSED_PARAM(pZone);
CCAssert(0, "not implement");
return 0;
}
CCCopying其实是个接口,里面并没有任何实现。这里说一下宏命令CC_UNUESD_PARAM(),查看他的定义,很容易发现
#define CC_UNUSED_PARAM(unusedparam) (void)unusedparam
就是说这个宏完全没有执行任何命令,这样写的原因主要是历史遗留原因,ojb-c不存在纯虚函数并且传入参数不使用编译器会发出警告,这样用一个宏既可以防止警告也有一定的解释作用
CCCopying并没有父类,这样说来CCObject算是继承树上比较高的结点了,它负责了内存管理部分的引用计数
//CCObject.cpp
CCObject::CCObject(void)
:m_uAutoReleaseCount(0)
,m_uReference(1) // 构造时,引用数置为1
,m_nLuaID(0)
{
static unsigned int uObjectCount = 0;
m_uID = ++uObjectCount;
}
void CCObject::release(void)
{
CCAssert(m_uReference > 0, "reference count should greater than 0");
--m_uReference;
if (m_uReference == 0)
{
delete this;
}
}
void CCObject::retain(void)
{
CCAssert(m_uReference > 0, "reference count should greater than 0");
++m_uReference;
}
可以看到,构造函数把引用数置为1,retain会把引用数加1,相反release会减1,每次release之后检测引用数,为0的话会析构当前对象。
另外cocos2d-x封装了一些容器,CCArray和CCDictionnary等,这些都是CCObject的容器,每当这些容器增加元素时,会调用一次retain,移除时会进行release
我们可以来对比一下下面的代码看一下引用计数和C++的new/delete的区别和优点
void function() {
int *arr[10];
int *num = new int(10);
arr[0] = num;
/*do something here*/
delete num;
int test = arr[0];/*arr[0]已经丢失*/
}
void function() {
CCArray *arr = CCArray::createWithCapacity(10);
CCObject *object = new CCObject; //引用 = 1
arr->addOjbect(object); //引用 = 2
/*do something here*/
object->release(); //引用 = 1,未被析构
CCObject *test = arr->getObjectAtIndex(0); //可以访问
arr->removeObjectAtIndex(0); //引用 = 0,析构
}
通过对比很容易发现两者不一样,通过引用计数,release和delete不同,release并未等于析构,当所有对象都不持有该对象的指针时,才析构该对象,下面,我们再看看CCObject的另一个关于内存管理的函数,autorealease
//CCObject.cpp
CCObject* CCObject::autorelease(void)
{
CCPoolManager::sharedPoolManager()->addObject(this);
return this;
}
autorelease并未对引用进行任何的操作,只见他放进了CCPoolManager里面,这个其实就是内存池的管理者,那么我们先来看一下内存池是怎样的
//CCAutoreleasePool.h
class CC_DLL CCAutoreleasePool : public CCObject
{
CCArray* m_pManagedObjectArray;
public:
CCAutoreleasePool(void);
~CCAutoreleasePool(void);
void addObject(CCObject *pObject);
void removeObject(CCObject *pObject);
void clear();
};
内存池其实就是一个CCArray的封装,代码很简单,这里就不贴源文件了,不过这里要注意几点
1.CCAutorelease是CCObject的子类,所以也是要进行引用计数等的管理的
2.尽管addObject和removeObject是向一个CCArray添加对象,可是内存池进行了一些操作,这两个操作并不会改变对象的引用数,也就是说,加入内存池并不改变对象的引用数
3.clear()就是内存池进行清理,会对内部所有的CCObject执行一次release()
//CCAutoreleasePool.h
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;
};
很高兴又见到了一个没有父类的家伙,从他的sharedPoolManager这个函数看来,他还是一个单件类,那就是名副其实的大哥类
内存管理在pop函数中体现
//CCAutoreleasePool.cpp
void CCPoolManager::pop()
{
if (! m_pCurReleasePool)
{
return;
}
int nCount = m_pReleasePoolStack->count();
m_pCurReleasePool->clear();
if(nCount > 1)
{
m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount - 2);
}
}
CCPoolManager就是一个CCAutorelease的栈,每次pop都把栈顶的内存池进行处理,把栈顶内存池管理的元素release掉。至于pop在什么时候调用?看过我写的 主流程(mainloop)分析可以知道——
CCDiretor在mainloop()中调用了drawScene()和CCPoolManager::sharedPoolManager()->pop()
也就是说在每帧结束后,就会进行内存的管理
总结来说,cocos2d-x内存管理的引用计数部分通过retain/release来进行计数增减,而内存池管理就通过autorelease来进行管理,在每帧结束时自动回收
最后提一句,cocos2d-x的宏CREATE_FUNC(className)会自动生成一个静态的create函数,把该类生成,然后自动调用init()和autorelease()
包括cocos2d-x的一些类的create函数都是如此实现的,但autorelease会增加程序对内存管理的消耗,所以一般来说最好不要使用create进行生成