转载自:http://www.jb51.cc/article/p-vacdhffy-bkt.html
Ref类是Cocos2d-x根类,Cocos2d-x中的很多类都派生自它,例如,我们熟悉的节点类Node也派生自Ref。我们介绍Ref内存管理。
内存引用计数
Ref类设计来源于Cocos2d-iPhone的CCObject类,在Cocos2d-x 2.x中也叫CCObject类。因此Ref类的内存管理是参考Objective-C手动管理引用计数(Reference Count)而设计的。
如图所示是内存引用计数原理示意图。
每个Ref对象都有一个内部计数器,这个计数器跟踪对象的引用次数,被称为“引用计数”(Reference Count,简称RC)。当对象被创建时候,引用计数为1。为了保证对象的存在,可以调用retain函数保持对象,retain会使其引用计数加1,如果不需要这个对象可以调用release函数,release使其引用计数减1。当对象的引用计数为0的时候,引擎就知道不再需要这个对象了,就会释放对象内存。
引用计数实例如图所示,我们在ObjA中使用new等操作创建了一个Ref对象,这时候这个对象引用计数为1。然后在OjbB中使用retain函数保持Ref对象,这时引用计数为2。再然后ObjA中调用release函数,这时引用计数为1。在ObjB中调用release函数,这时引用计数为0。这个时候Ref对象就会由引擎释放。
在Ref类中相关函数有:retain()、release()、autorelease()和getReferenceCount()。其中autorelease()函数与release()函数类似,它会延后使引用计数减1,autorelease()我们稍后再介绍。getReferenceCount()函数返回当前的引用计数。
自动释放池
我们先看看下面的代码片段。
1
2
3
4
5
6
|
XmlParser*XmlParser::createWithFile(
const
char
*fileName)
{
XmlParser*pRet=
new
XmlParser();
// ①
return
pRet;
}
|
上述代码XmlParser::createWithFile(const char *fileName)函数能够创建XmlParser对象指针并返回给调用者。根据我们前面介绍的C++使用new规则,在XmlParser::createWithFile函数中还应该释放对象的语句,如果没有,那么每次调用者调用XmlParser::createWithFile函数都会创建一个新对象,老的对象没有释放,就会造成内存泄漏。但是如果我们在第①行代码,添加释放语句pRet->release(),那么问题可能会更严重,返回的对象可能已经被释放,返回的可能是一个野指针。
自动释放池(AutoReleasePool)正是为此而设计,自动释放池也是来源于Objective-C,Cocos2d-x中维护AutoreleasePool对象,它能够管理即将释放的对象池。我们在第①可以使用pRet->autorelease()语句,autorelease()函数将对象放到自动释放池,但对象的引用计数并不马上减1,而是要等到一个消息循环结束后减1,如果引用计数为0(即,没有被其它类或Ref对象retain),则释放对象,在此之前对象并不会释放。
消息循环是游戏循环一个工作职责,消息循环说到底还是游戏循环,消息循环是接收事件,并处理事件。自动释放池的生命周期也是由消息循环管理的。如图所示,图中“圈圈”是消息循环周期,它的一个工作职责是维护自动释放池创建和销毁。每次为了处理新的事件,Cocos2d-x引擎都会创建一个新的自动释放池,事件处理完成后,就会销毁这个池,池中对象的引用计数会减1,如果这个引用计数会减0,也就是没有被其它类或Ref对象retain,则释放对象,否则这个对象不会释放,在这次销毁池过程中“幸存”下来,它被转移到下一个池中继续生存。
下面我们看一个实例,下面代码是实例HelloWorldScene.cpp代码:
bool
HelloWorld::init()
if
(!Layer::init())
{
return
false
;
}
SizevisibleSize=Director::getInstance()->getVisibleSize();
Vec2origin=Director::getInstance()->getVisibleOrigin();
autogoItem=MenuItemImage::create(
"go-down.png"
,
"go-up.png"
goItem->setPosition(Vec2(origin.x+visibleSize.width-goItem->getContentSize().width/2,
origin.y+goItem->getContentSize().height/2));
automenu=Menu::create(goItem,NULL); ①
menu->setPosition(Vec2::ZERO);
->addChild(menu,1); ②
->list=__Array::createWithCapacity(MAX_COUNT);
->list->retain(); ③
for
(
int
i=0;i<MAX_COUNT;++i){
Sprite*sprite=Sprite::create(
"Ball.png"
);
->list->addObject(sprite); ④
}
true
;
}
void
HelloWorld::menuCloseCallback(Ref*pSender)
{
Ref*obj=NULL;
log
(
"list->count()=%d"
->list->count()); ⑤
SizevisibleSize=Director::getInstance()->getVisibleSize();
CCARRAY_FOREACH(
->list,obj){
Sprite*sprite=(Sprite*)obj; ⑥
x=CCRANDOM_0_1()*visibleSize.width;
y=CCRANDOM_0_1()*visibleSize.height;
sprite->setPosition(Vec2(x,y));
->removeChild(sprite);
->addChild(sprite);
}
}
HelloWorld::~HelloWorld()
{
->list->removeAllObjects();
CC_SAFE_RELEASE_NULL(
->list); ⑦
在上述代码中我们需要关注两个对象(Menu和__Array)创建。第①行auto menu = Menu::create(goItem,NULL)通过create静态工厂创建对象,关于静态工厂的创建原理我们会在下一节介绍。如果我们不采用第②行的this->addChild(menu,1)语句将menu 对象放入到当前层(HelloWorld)的子节点列表中,那么这个menu对象就会在当前消息循环结束的时候被释放。调用this->addChild(menu,1)语句会将它的生命周期持续到HelloWorld层释放的时候,而不会在当前消息循环结束释放。
|