Cocos2d-x开发中Ref内存管理

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()函数类似它会延后使引用计数减1autorelease()我们稍后再介绍。getReferenceCount()函数返回当前的引用计数。

自动释放池
我们先看看下面的代码片段。

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-CCocos2d-x中维护AutoreleasePool对象它能够管理即将释放的对象池。我们在第①可以使用pRet->autorelease()语句autorelease()函数将对象放到自动释放池但对象的引用计数并不马上减1而是要等到一个消息循环结束后减1如果引用计数为0即没有被其它类或Ref对象retain则释放对象在此之前对象并不会释放。
消息循环是游戏循环一个工作职责消息循环说到底还是游戏循环消息循环是接收事件并处理事件。自动释放池的生命周期也是由消息循环管理的。如图所示图中“圈圈”是消息循环周期它的一个工作职责是维护自动释放池创建和销毁。每次为了处理新的事件Cocos2d-x引擎都会创建一个新的自动释放池事件处理完成后就会销毁这个池池中对象的引用计数会减1如果这个引用计数会减0也就是没有被其它类或Ref对象retain则释放对象否则这个对象不会释放在这次销毁池过程中“幸存”下来它被转移到下一个池中继续生存。

下面我们看一个实例下面代码是13.2.2一节的实例HelloWorldScene.cpp代码

bool HelloWorld::init()
{
	if ( !Layer::init() )
	{
		return false;
	}

	Size visibleSize = Director::getInstance()->getVisibleSize();
	Vec2 origin = Director::getInstance()->getVisibleOrigin();

	auto goItem = MenuItemImage::create(
		"go-down.png",
		"go-up.png",
		CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));

	goItem->setPosition(Vec2(origin.x + visibleSize.width - goItem->getContentSize().width/2 ,
		origin.y + goItem->getContentSize().height/2));

	auto menu = Menu::create(goItem, NULL);									①
	menu->setPosition(Vec2::ZERO);
	this->addChild(menu, 1);												②

	this->list  = __Array::createWithCapacity(MAX_COUNT);
	this->list->retain();													③

	for(int i = 0;i < MAX_COUNT; ++i){
		Sprite* sprite = Sprite::create("Ball.png");
		this->list->addObject(sprite); 											④
	}

	return true;
}

void HelloWorld::menuCloseCallback(Ref* pSender)
{
	Ref* obj = NULL;
	log("list->count() = %d",this->list->count());									⑤
	Size visibleSize = Director::getInstance()->getVisibleSize();

	CCARRAY_FOREACH(this->list, obj) {

		Sprite* sprite = (Sprite*)obj; 											⑥

		int x = CCRANDOM_0_1() * visibleSize.width;
		int y = CCRANDOM_0_1() * visibleSize.height;

		sprite->setPosition( Vec2(x, y) );
		this->removeChild(sprite);
		this->addChild(sprite);
	}

}

HelloWorld::~HelloWorld()
{
	this->list->removeAllObjects();
	CC_SAFE_RELEASE_NULL(this->list);										⑦
}

在上述代码中我们需要关注两个对象Menu和__Array创建。第①行auto menu = Menu::create(goItem, NULL)通过create静态工厂创建对象关于静态工厂的创建原理我们会在下一节介绍。如果我们不采用第②行的this->addChild(menu, 1)语句将menu 对象放入到当前层HelloWorld的子节点列表中那么这个menu对象就会在当前消息循环结束的时候被释放。调用this->addChild(menu, 1)语句会将它的生命周期持续到HelloWorld层释放的时候而不会在当前消息循环结束释放。
菜单、层等节点对象可以调用addChild函数使得其生命延续。而且__Array和__Dictionary等Ref对象没有调用addChild函数保持我们需要显式地调用retain函数保持它们以便延续其生命。如代码第③行this->list->retain()list就是一个__Array指针类型的成员变量如果没有第③行语句那么在第⑤行代码this->list->count()程序就会出错因为这个时候list对象已经释放了。采用了retain保持的成员变量一定要release或autoreleaseretain和release或autorelease一定是成对出现的。我们可以在析构函数~HelloWorld()中调用release释放而第⑦行代码CC_SAFE_RELEASE_NULL(this->list)就是实现这个目的其中CC_SAFE_RELEASE_NULL宏作用如下
list->release();
list = nullptr;
可见CC_SAFE_RELEASE_NULL宏不仅仅释放对象还将它的指针设置为nullprt[]也样可以防止野指针。
上述代码还有一个非常重要的关于内存的问题我们在HelloWorld::init()函数中创建了很多Sprite对象通过第④行代码this->list->addObject(sprite)将它们放到list容器对象中它们没有调用addChild函数也没有显式retain函数然而在当前消息循环结束后它们还是“存活”的所以在第⑥行Sprite* sprite = (Sprite*)obj中取出的对象是有效的。这个原因__Array和__Dictionary等容器对象的add相关函数可以使添加对象引用计数加1相反的remove相关函数可以使添加对象引用计数减1。

Ref内存管理规则
下面我们给出使用Ref对象时候内存管理一些基本规则

1、在使用Node节点对象时候addChild函数可以保持Node节点对象使引用计数加1。通过removeChild函数移除Node节点对象使引用计数减1。它们都是隐式调用的我们不需要关心它们的内存管理。这也正是为什么在前面的章节中我们无数次地使用了Node节点对象而从来都没有担心过它们内存问题。

2、如果是__Array和__Dictionary等容器对象可以通过它们add相关函数添加元素会使引用计数加1相反的remove相关函数删除元素会使引用计数减1。但是前提是__Array和__Dictionary等容器对象本身不没有被释放。

3、如果不属于上面提到的Ref对象需要保持引用计数可以显式调用retain函数使引用计数加1然后显式调用release或autorelease函数使引用计数减1。

4、每个 retain函数一定要对应一个 release函数或一个 autorelease函数。

5、release函数使得对象的引用计数马上减1这是所谓的“斩立决”但是是否真的释放掉内存要看它的引用计数是否为0。autorelease函数只是在对象上做一个标记等到消息循环结束的时候再减1这是所谓的“秋后问斩”在“秋天”没有到来之前它的内存一定没有释放可以安全使用但是“问斩”之后是否真的释放掉内存要看它的引用计数是否为0。因此无论是那一种方法引用计数是为0才是释放对象内存的条件。下面的代码是Ref类的release函数通过这段代码可以帮助我们理解引用计数。

void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should greater than 0");
    --_referenceCount;

    if (_referenceCount == 0)
    {
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
        auto poolManager = PoolManager::getInstance();
        if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
        {
            // Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.
            // This happens when 'autorelease/release' were not used in pairs with 'new/retain'.
            //
            // Wrong usage (1):
            //
            // auto obj = Node::create();   // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool.
            // obj->autorelease();   // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first.
            //
            // Wrong usage (2):
            //
            // auto obj = Node::create();
            // obj->release();   // Wrong: obj is an autorelease Ref, it will be released when clearing current pool.
            //
            // Correct usage (1):
            //
            // auto obj = Node::create();
            //                     |-   new Node();     // `new` is the pair of the `autorelease` of next line
            //                     |-   autorelease();  // The pair of `new Node`.
            //
            // obj->retain();
            // obj->autorelease();  // This `autorelease` is the pair of `retain` of previous line.
            //
            // Correct usage (2):
            //
            // auto obj = Node::create();
            // obj->retain();
            // obj->release();   // This `release` is the pair of `retain` of previous line.
            CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
        }
#endif
        delete this;
    }
}

6、一个对象调用autorelease函数它就会将对象放到自动释放池里它生命周期自动释放池生命周期息息相关池在消息循环结束的时候会释放池会调用池内对象release函数使得它们的引用计数减1。下面的代码是AutoreleasePool类的清除函数clear()通过这段代码可以帮助我们理解自动释放池机制。

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = true;
#endif
    for (const auto &obj : _managedObjectArray)
    {
        obj->release();
    }
    _managedObjectArray.clear();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = false;
#endif
}

更多内容请关注最新Cocos图书《Cocos2d-x实战 C++卷》‍

本书交流讨论网站http://www.cocoagame.net

更多精彩视频课程请关注智捷课堂Cocos课程http://v.51work6.com

欢迎加入Cocos2d-x技术讨论群257760386

《Cocos2d-x实战 C++卷》现已上线各大商店均已开售‍

京东http://item.jd.com/11584534.html

亚马逊http://www.amazon.cn/Cocos2d-x%E5%AE%9E%E6%88%98-C-%E5%8D%B7-%E5%85%B3%E4%B8%9C%E5%8D%87/dp/B00PTYWTLU

当当http://product.dangdang.com/23606265.html

互动出版网http://product.china-pub.com/3770734

《Cocos2d-x实战 C++卷》源码及样章下载地址

源码下载地址http://51work6.com/forum.php?mod=viewthread&tid=1155&extra=page%3D1 

样章下载地址http://51work6.com/forum.php?mod=viewthread&tid=1157&extra=page%3D1

欢迎关注智捷iOS课堂微信公共平台

时间: 2024-11-11 03:14:15

Cocos2d-x开发中Ref内存管理的相关文章

Cocos2d-x开发中C++内存管理

由于开始并没有介绍C++语言C++的内存管理当然也没进行任何的说明为了掌握Cocos2d-x中的内存管理机制是有必要先了解一些C++内存管理的知识.C++内存管理非常复杂如果完全地系统地介绍可能需要一本书的篇幅才能解释清楚.这里只给大家介绍C++内存管理最为基本的用法. 内存分配区域创建对象需要两个步骤第一步为对象分配内存第二步调用构造函数初始化内存.在第一步中对象分配内存时候我们可以选择几个不同的分配区域这几个区域如下栈区域分配.栈内存分配运算内置于处理器的指令集中效率很高但是分配的内存容量有

cocos2d x-cocos2dx-cpp中的内存管理问题

问题描述 cocos2dx-cpp中的内存管理问题 请教一下:在GitHub中的cocos2dx-cpp中的cocos2d-x / tests / cpp-tests / Classes / AccelerometerTest / AccelerometerTest.cpp的代码`AccelerometerTestScene::runThisTest(){auto layer = new AccelerometerTest();addChild(layer);layer->release();/

解析PHP中的内存管理,PHP动态分配和释放内存

本篇文章是对PHP中的内存管理,PHP动态分配和释放内存进行了详细的分析介绍,需要的朋友参考下   摘要 内存管理对于长期运行的程序,例如服务器守护程序,是相当重要的影响:因此,理解PHP是如何分配与释放内存的对于创建这类程序极为重要.本文将重点探讨PHP的内存管理问题. 一. 内存在PHP中,填充一个字符串变量相当简单,这只需要一个语句"<?php $str = 'hello world '; ?>"即可,并且该字符串能够被自由地修改.拷贝和移动.而在C语言中,尽管你能够

C++中的内存管理

在C++中也是少不了对内存的管理,在C++中只要有new的地方,在写代码的时候都要想着delete. new分配的时堆内存,在函数结束的时候不会自动释放,如果不delete我分配的堆内存,则会造成内存泄露.所以我们要学会内存管理,不要内存泄露.在C++中的内存管理机制和OC中的还不太一样,在OC中的ARC机制会给程序员的内存管理省不少事,但在C++中没有ARC所以我们要自己管理好自己开辟的内存.Java中也有自己相应的内存管理机制,比如JDBC里的获取的各种资源在finally里进行close等

Open Cascade中的内存管理

Open Cascade中的内存管理 Memory Management in Open Cascade eryar@163.com 一.C++中的内存管理 Memory Management in C++ 1. 引言 为了表现出多态,在C++中就会用到大量的指针和引用.指针所指的对象是从内存空间中借来的,当然要及时归还.特别是指针在程序中随心所欲地创建,因此,一个指针究竟指向哪个对象,一个对象到底被几个指针所指向,是程序员十分关注的事情. C++中涉及到的内存管理问题可以归结为两方面:正确地掌

模拟实现C语言中的内存管理_C 语言

这里模拟了C语言中的内存管理,当我们要创建或者使用一个对象时,那么这个对象会调用retain方法,计数+1,当我们要释放对象,我们会调用free,这里注意要对计数记性判断,如果是0的话,那么就会销毁. #import <Foundation/Foundation.h> int cnt = 0; void fun (charchar * p) { printf("%c\n",p[0]); } charchar * retain1(charchar * p) { //retai

Linux内核中的内存管理浅谈

 [十月往昔]--Linux内核中的内存管理浅谈 为什么要叫做"十月往昔"呢?是为了纪念我的原博客. 不知道为什么,突然想来一个新的开始--而那个博客存活至今刚好十个月,也有十个月里的文档. 十月往昔,总有一些觉得珍贵的,所以搬迁到这里来. 而这篇文章是在09.04.20-09.04.21里写的. Jason Lee   ------------–cut-line   1.基本框架(此处主要谈页式内存管理) 4G是一个比较敏感的字眼,早些日子,大多数机器(或者说操作系统)支持的内存上限

6.关于QT中的内存管理,动态的制作,动态库的调用,静态库的制作

 一  QT的内存管理 1  QT中的内存管理是QObject来管理的 2  QT中的内存管理没有cocos2dx中的引用计数 3  组件可以指定父对象 QTimer *timer = QTimer(this);   //这里的this实际上强制转换成为了QObject了. 4  每一个对象都有一个deleteLater()方法, QLineEdit* edit = new QLineEdit(this);   delete edit;   //这里的delete马上调用析构函数对内存进行释

通过案例深入探讨PHP中的内存管理问题

问题  内存管理对于长期运行的程序,例如服务器守护程序,是相当重要的影响:因此,理解PHP是如何分配与释放内存的对于创建这类程序极为重要.本文将重点探讨PHP的内存管理问题. 一. 内存 在PHP中,填充一个字符串变量相当简单,这只需要一个语句"<?php $str = 'hello world '; ?>"即可,并且该字符串能够被自由地修改.拷贝和移动.而在C语言中,尽管你能够编写例如"char *str = "hello world ";&