内存管理中经常遇到的问题:内存泄露,内存溢出。
在cocos2dx中用的是引用计数和自动释放池的技术,由于熟悉objective-c语言,所以对这两个概念不会很陌生。
一、引用计数
引用计数是自动内存管理的基础:在对象里增加一个引用计数,当外部引用增加时,计数器加1,当外部引用消失时,计数器减1 。
看一下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
unsigned int m_uAutoReleaseCount;
public:
CCObject (void);
/**
* @lua NA
*/
virtual ~CCObject( void);
void release (void);
void retain (void);
CCObject * autorelease( void);
CCObject * copy( void);
bool isSingleReference (void) const;
unsigned int retainCount( void) const;
virtual bool isEqual( const CCObject* pObject);
virtual void acceptVisitor( CCDataVisitor &visitor );
virtual void update( float dt) {CC_UNUSED_PARAM (dt);};
friend class CCAutoreleasePool;
};
其中m_uReference就是用于计数的整形变量,在这里就记录了当前对象的引用计数。
跟objective-c一样,retain是计数器加1, release是计数器减1, 计数器为0的时候会自动清除对象。
加1 减1 操作如下:
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 ;
}
上面的引用计数只是自动管理的方式,通过这种方式cocos2dx引擎会晓得何时释放对象。
在这里之前需要保证引用计数的准确性,建议:
1、开始对应着结束,创建(new,copy)了对象就一定要释放(release)。
2、引用了就要释放, 也就是说 调用了retain后就要调用release。
3、参数传递需要更替引用。 当对象指针作为参数传递的时候,在函数内需要引用对象,这时候就需要释放掉旧的对象,然后增加新的对象:
void a(B *b) {
retain(b);
release(m_b);
m_b = b;
}
来解释一下上面函数为何先增加 再减少引用计数,然后赋值:
1、retain(b)是因为 b指向一个B 对象,retain一次是为了保证b不被销毁。保留一次值。
2、release(m_b) 是因为m_b是一个属性,在这里实际上是要释放掉上次赋给他的值。
3、这里是m_b和b指向相同的对象,拥有相同的引用计数。 调用完毕后,他们的引用计数是1。
二、自动释放池
CCPoolManager 这个类是自动释放吃计数的精髓。
我们可以将对象放在自动释放吃中,在引擎每次绘制周期结束的时候,就会自动释放池中的对象。
具体的使用:
CCObject *obj = new CCObject;
obj->autorelease(); 将对象加入自动释放池中。
1、原理
如上面代码,创建一个obj对象,通过autorelease将他加入到自动释放池中, 使用函数设置自动释放功能时,内存管理类CCPoolManager就会把这个当前对象加入到管理池中,等到特定的时候,内存管理着就会遍历其所管理的每一个对象,逐个调用对象的释放函数进行释放。
CCObject* CCObject ::autorelease( void)
{
CCPoolManager ::sharedPoolManager()-> addObject(this );
return this;
}
再看addObject函数:
void CCPoolManager:: addObject(CCObject * pObject)
{
getCurReleasePool ()->addObject( pObject);
}
CCAutoreleasePool* CCPoolManager ::getCurReleasePool()
{
if(! m_pCurReleasePool)
{
push ();
}
CCAssert (m_pCurReleasePool, "current auto release pool should not be null");
return m_pCurReleasePool ;
}
void CCAutoreleasePool:: addObject(CCObject * pObject)
{
m_pManagedObjectArray ->addObject( pObject);
CCAssert (pObject-> m_uReference > 1, "reference count should be greater than 1" );
++(pObject ->m_uAutoReleaseCount);
pObject ->release(); // no ref count, in this case autorelease pool added.
}
可以看到,
CCArray
*
m_pManagedObjectArray
; 这是一个数组;
在上面函数的最后一句调用了release, 这是因为 在前面addObject添加到数组的时候,增加了一次对象的引用计数。
下面看看对象自动释放的过程:
void
CCPoolManager
::
push
()
{
CCAutoreleasePool
*
pPool
=
new
CCAutoreleasePool
();
//ref = 1
m_pCurReleasePool
=
pPool
;
m_pReleasePoolStack
->
addObject
(
pPool
);
//ref = 2 加入释放池容器
pPool
->
release
();
//ref = 1
}
void
CCPoolManager
::
pop
()
{
if
(!
m_pCurReleasePool
)
{
return
;
}
int
nCount
=
m_pReleasePoolStack
->
count
(); //获取当前释放池中对象的数目
m_pCurReleasePool
->
clear
(); //释放自动释放池中的内容
if
(
nCount
>
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;*/
}
代码中
,
m_pReleasePoolStack
是保存多个释放吃的地方, 要注意第一个自动释放池实在CCDirector构造函数中创建的。
在CCPoolManager类中,你会看到他可以保存多个自动释放池, 自动释放池中保存的是对象。 所以自动释放吃的创建和释放是由Manager来负责的。
来看一下是怎么释放的:
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);
//(*it)->release();
//delete (*it);
#ifdef _DEBUG
nIndex --;
#endif
}
m_pManagedObjectArray ->removeAllObjects(); //释放池中的所有对象
}
}
在这里,在释放之前,pObject的标记autorelease count会减1 。 这样是为了避免释放对象后产生错误。 所以,自动释放池技术也不是永远都可以依赖的,他也是依赖引用计数,只要引用存在,内存就不能回收。
cocos2dx 引擎通过管理模式来实现自动释放池的技术。 在这里很多地方都使用了管理模式。 管理模式+缓冲区 可以很好地管理内存资源。
三、管理模式
在cocos2dx中,不仅仅只有前面使用的CCPoolManager, 还有动作管理者,脚本管理者等等。
1、在引擎当中,只会存在一个管理者,如:自动释放池管理者,动作管理者等等。 为了保证管理者的唯一性,管理者模式的对象都会是单例。 通过share前缀函数获得单例,purge前缀函数释放单例。
2、管理模式可以管理对象,他会负责所管理对象的创建和销毁。
3、管理模式的优点是为一组相关的对象提供一个统一的全局访问点,同时可以提供一些简洁的接口来获取和操作这些对象。 用他来缓存游戏中的常有资源,可以提高游戏运行时的性能。
看看缓冲区:
游戏中有的资源不会被常用,只出现几次,但是有的资源经常会出现,这样我们就可以将他们放在缓冲区了。
这样存在内存不够的情况,这样在内存占用过多的时候可以来释放相应的缓冲区。
在缓冲区的内部也使用了引用计数的方式来管理对象资源,可以通过retain和release来进行控制。
我们也可以通过缓冲区进行预加载的操作。 但是要注意清理。