Cocos2Dx的所有的类都直接或间接继承自CCObject,让类具有自动的内存管理能力。根本上讲,是借鉴自苹果的设计思维。从iPhone版的Cocos2Dx,到C++版的Cocos2Dx,以及到后面的Js,HTML5,保持API的一致性可以帮助开发者快速适应或者选择新的开发语言。沿袭的Objective-C风格个人觉得很不错,采用C++开发就是需要有自己的风格,或者叫规范,来指导开发者,避免迷失在C++的丛林中。
@H_403_2@ Cocos2Dx的内存管理有两个思路:一是引用计数,二是自动回收池。引用计数来记录对象是否还被引用,如果没有被引用就释放对象。自动回收池是将对象的引用释放进行了托管。 ?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class CC_DLLCCObject: public CCcopying { : unsigned int m_uID; protected : m_uReference; m_uautoReleaseCount; : CCObject( voID ); virtual ~CCObject( ); release( ); retain( ); CCObject*autorelease( ); bool isSingleReference( ) const ; retainCount( ; frIEnd CCautoreleasePool; }; |
CCObject::CCObject(
)
:m_uReference(1)
,m_uautoReleaseCount(0)
{
static
uObjectCount=0;
m_uID=++uObjectCount;
}
{
CCSprite*pSprite=
new
CCSprite();
if
(pSprite&&pSprite->init())
{
pSprite->autorelease();
return
pSprite;
}
CC_SAFE_DELETE(pSprite);
NulL;
}
#defineCC_SAFE_DELETE(p)do{if(p){delete(p);(p)=0;}}while(0)
CCPoolManager::sharedPoolManager()->addobject(
this
);
return
;
CCPoolManager::sharedPoolManager()返回对象池Manager单例。单例模式的使用在Cocos2Dx也很多。本质上单例就是全局变量,提供一个封装好的入口而已。CCPoolManager维护了一个栈式的对象池。 ? 17 18 19 CCPoolManager::addobject(CCObject*pObject) getCurReleasePool()->addobject(pObject);
}
CCautoreleasePool*CCPoolManager::getCurReleasePool()
{
(!m_pCurReleasePool)
{
push();
}
m_pCurReleasePool;
}
CCPoolManager::push()
{
CCautoreleasePool*pPool=
CCautoreleasePool();
//CCautoreleasePool的CCObject的引用计数是1
m_pCurReleasePool=pPool;
m_pReleasePoolStack->addobject(pPool);
//内部调用pPool的retain(),将CCObject的引用计数是暂时提升到2
pPool->release();
//CCautoreleasePool的CCObject的引用计数是1。需要注意的是CCautoreleasePool虽然是一个对象容器,但是并没有增加对象的引用计数,只是增加了自动引用计数
CCPoolManager的push()和pop()负责维护对象自动回收池栈。CCPoolManager内部使用的是CCArray来做数据存储。栈顶位于数组的后面(索引值大),栈底位于数组的前面。调用autorelease()将对象添加到当前栈顶的对象池当中时,如果当前对象回收池池m_pCurReleasePool为空,就调用push创建一个对象回收池,然后将其添加到CCPoolManager中。CCPoolManager返回当前自动回收池给CCObject的autorelease()添加对象。 @H_403_2@ 注意push()的代码,由于使用的new,我们就需要自己维护好引用计数。所有容器的addobject方法,内部都会增加添加对象的引用计数。这是可以理解的,因为相对于现在容器也有了到对象的引用。push()后面又调用了release(),是因为pPool指针对其的引用来跳出代码块以后已经无效,从外部看,只有容器对其存在唯一的引用。这就是对象所有权的传递。所有权传递需要特别小心,不然会导致引用计数错误,进而使对象过早被释放,甚至不再被释放。管理内存是额外的负担,如果没有合适的理由,不要自己去new对象,然后维护引用计数。繁琐的细节应该被隐藏起来。 6
CCautoreleasePool::addobject(CCObject*pObject) m_pManagedobjectArray->addobject(pObject);
++(pObject->m_uautoReleaseCount);
pObject->release();
CCautoreleasePool内部也是通过CCArray来存储被托管进来的对象。CCArray的addobject(pObject)会将对象pObject的引用计数加1,所以后面会调用release()将引用计数减1。 15
CCArray::addobject(CCObject*object) ccArrayAppendobjectWithResize(data,object);
}
ccArrayAppendobjectWithResize(ccArray*arr,CCObject*object)
ccArrayEnsureExtraCapacity(arr,1);
ccArrayAppendobject(arr,object);
}
ccArrayAppendobject(ccArray*arr,CCObject*object)
{
object->retain();
arr->arr[arr->num]=object;
arr->num++;
现在是时候看看release()和retain()的实现了。 CCObject::release(
--m_uReference;
(m_uReference==0)
delete
;
}
}
CCObject::retain(
)
{
++m_uReference;
release()和retain()就是将引用计数加1和减1。
到现在我们看到release()会删除对象,autorelease()会将对象添加到自动回收池当中。但是什么对象回收池什么时候去真正释放托管的那些对象呢?我们是不是需要一个单独地线程来做对象回收池回收?Cocos2Dx在CCdisplaylinkDirector::mainLoop(voID)当中调用CCPoolManager::sharedPoolManager()->pop()完成一次回收池释放的。 @H_403_2@ 我们以WIN32为例,看看Cocos2Dx的消息循环是如何处理对象回收池的。 @H_403_2@ 应用的消息循环可以分为两层: @H_404_540@ 一是CCApplication::run()。主要完成 *** 作系统相关的消息获取、分发等功能。这一层是平台相关的,每个平台有自己不同的实现。 @H_404_540@ 二是CCDirector::sharedDirector()->mainLoop()。这是Cocos2Dx的主循环,负责游戏画面的绘制、事件调度等。这一层是Cocos2Dx的核心运行机制。这一层跟前一层是嵌套关系,但run内的循环获取到 *** 作系统消息一次,并不对应着mainLoop调用一次。mainLoop的调用时机由游戏的帧间隔决定,如果没有达到帧间隔时间,mainLoop是不会被调用的。 @H_403_2@ 完整的启动流程我们在下一篇文章中讨论。现在直接看CCDirector::sharedDirector()->mainLoop()。 13
CCdisplaylinkDirector::mainLoop( (m_bPurgeDirecotorInNextLoop)
m_bPurgeDirecotorInNextLoop=
false
;
purgeDirector();
}
else
(!m_bInvalID)
{
drawScene();
CCPoolManager::sharedPoolManager()->pop();
}
mainLoop前面判断是否调用了CCDirector的end(),即游戏结束。如果是,CCDirector做一些清理工作,否则就绘制场景,并且调用CCPoolManager::pop()来释放一次内存池。 14
CCPoolManager::pop() (!m_pCurReleasePool)
}
nCount=m_pReleasePoolStack->count();
m_pCurReleasePool->clear();
(nCount>1)
m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
m_pCurReleasePool=(CCautoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount-2);
}
pop() 首先检查当前是否有活动的对象回收池,如果没有就什么都不做。如果我们没有调用过autorelease(),没有去托管一些对象到对象回收池,就会存在这种情况。然后调用对象回收池CCautoreleasePool的clear()函数。clear()遍历对象回收池,将每个对象的m_uautoReleaseCount减1。然后将这些对象从对象回收池的内部存储容器CCArray中删除。 @H_403_2@ 调用了pop(),我们只是岁栈顶的对象回收池进行了释放。栈里面的其他对象池仍然存在,没有任何变化。但需要调整一下当前对象回收池,m_pCurReleasePool应该是现在位于栈顶的回收池了。 CCautoreleasePool::clear()
(m_pManagedobjectArray->count()>0)
CCObject*pObj=NulL;
CCARRAY_FOREACH_REVERSE(m_pManagedobjectArray,pObj)
{
(!pObj)
break
;
--(pObj->m_uautoReleaseCount);
}
m_pManagedobjectArray->removeAllObjects();
前面看到CCArray::addobject会调用retain增加引用计数,与此对应,CCArray的所有删除接口都会调用对象的release()来减少引用计数,并且在引用计数为0的时候释放对象。 11
CCArray::removeAllObjects() ccArrayRemoveAllObjects(data);
ccArrayRemoveAllObjects(ccArray*arr)
while
(arr->num>0)
(arr->arr[--arr->num])->release();
}
通过前面的分析,可以看到,retain()和release()分别用来获取对象和释放对象。autorelease()用来添加对象到自动回收池,对象回收池在每隔帧间隔时间统一释放一次回收池内的对象。单单使用这三个函数,开发者还是有内存维护的代价在里面。Cocos2Dx进一步利用一些编码指导规范来帮助大家简化内存维护的代价。 @H_403_2@ 前面提到过,自己通过new来构造一个对象需要自己做一些额外的工作。推荐使用静态的create()来构造对象。create()构造对象都是先new出对象,然后调用对象的init()函数做初始化,然后添加到对象回收池。Cocos2Dx提供了CREATE_FUNC来帮助我们完成create()函数的。使用非常方便,只需要在类的public成员里面插入这样一条宏即可。 16
#defineCREATE_FUNC(__TYPE__)\
__TYPE__*create()\
{\
__TYPE__*pRet=
__TYPE__();\
(pRet&&pRet->init())\
{\
pRet->autorelease();\
pRet;\
}\
else
\
{\
delete
pRet;\
pRet=NulL;\
NulL;\
}\
有意思的是,Cocos2Dx还提供了一些其他非常有用的宏:CC_SYNTHESIZE帮助创建Setter/Getter,CCARRAY_FOREACH帮助遍历数组,CC_SAFE_DELETE等帮助释放对象,CC_BREAK_IF做条件判断。还有很多宏,可以学习借鉴下。 @H_403_2@ 最后我们用例子来结束内存管理这部分。 @H_403_2@ 实例: cclayer*backAction()
sceneIDx--;
total=MAX_LAYER;
(sceneIDx<0)
sceneIDx+=total;
cclayer*pLayer=(createFunctions[sceneIDx])();
//构造一个cclayer,没有使用CREATE_FUNC宏,引用计数为1,自动引用计数为0
pLayer->autorelease();
//添加到对象回收池当中,引用计数为1,自动引用计数为1
pLayer;
}
LayerTest::backCallback(CCObject*pSender)
{
CCScene*s=
LayerTestScene();
//创建一个LayerTestScene对象,引用计数为1,自动引用计数为0
s->addChild(backAction());
//cclayer的引用计数增加1,引用计数为2,自动引用计数为1
CCDirector::sharedDirector()->replaceScene(s);
//LayerTestScene的引用计数增加1,引用计数为2,自动引用计数为0
s->release();
//LayerTestScene的引用计数减少1,引用计数为1,自动引用计数为0
cclayer的引用计数为2,自动引用计数为1。在过一个帧间隔之后,CCautoreleasePool::pop()被调用,进而调用CCautoreleasePool::clear(),最终调用CCArray::removeAllObjects(),对所有CCArray中的对象调用一次release()。cclayer的引用计数为1,自动引用计数为0。LayerTestScene继承结构中有CCNode,它的析构函数会释放掉所有的孩子节点: @H_403_2@ CC_SAFE_RELEASE(m_pChildren) @H_403_2@ ->CCArray.release() @H_403_2@ ->ccArrayFree(data) @H_403_2@ ->ccArrayRemoveAllObjects(arr) @H_403_2@ ->cclayer.release() @H_403_2@ cclayer现在的引用计数为1,自动引用计数为0,调用release()就会释放cclayer。可以看到cclayer的释放,依赖于父节点LayerTestScene的释放。 @H_403_2@ LayerTestScene的引用计数为1,自动引用计数为0。当有新的Scene需要显示的时候,会做Scene的切换,旧的Scene会被CC_SAFE_RELEASE删除(调用release())。 总结 以上是内存溢出为你收集整理的Cocos2Dx之内存管理-欧阳左至全部内容,希望文章能够帮你解决Cocos2Dx之内存管理-欧阳左至所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
赞
(0)
打赏
微信扫一扫
支付宝扫一扫
[cocos2dx]我的学习记录
上一篇
2022-05-26
cocos2dx之旋转的button
下一篇
2022-05-26
评论列表(0条)