cocos2d-x游戏引擎核心之二——内存管理
(一) cocos2d-x 内存管理
cocos2d里面管理内存采用了引用计数的方式,具体来说就是CCObject里面有个成员变量m_uReference(计数);
1, m_uReference的变化
对象初始化:m_uReference = 1
retain:++m_uReference
release:--m_uReference,if(m_uReference == 0) delete 对象
addxxx:一般具有add语义代码会retain对象,比如说addChild
removexxx:一般具有remove语句的代码会release对象
2, CCPoolManager(以池的方式管理内存)
cocos2d-x采用了以池的方式管理内存,前提是给对象执行autorelease操作,这个时候对象有个属性m_bManaged = true;他和我们手动的release有什么区别了?其实也就是调用时机不一样,CCPoolManager会在每帧动画结束后调用pop方法将池中的对象release掉:
在CCPoolManager的clear方法中首先会遍历所有池中元素,将m_bManaged =
false(注意这里),后将所有池中的所有对象release掉,当对象的 m_bManaged == false 的时候,以前在池中的对象现在就不会再在池中,也就是说下一个帧的动画结束后,被先前CCPoolManager调用pop方法release的对象(如果还存在的话,因为可能被其他对象指引)就不会再被pop方法release了(CCPoolManager的最终作用就是将对象的引用计数减1而已,除此之外没有什么其他神奇的功能)
当父节点被析构时,其所有的子节点都会相应执行release,后将父节点重置为NULL.
coco2d-x中很多用静态方法创建的对象都会自动由CCPoolManager来管理内存,举个例子:
在我们的操作下,不是说由CCPoolManager管理的内存就是没有问题的,他和我们手动release就是一个调用时机的差别,而我们所要关注的是当前程序的运行环境和对象的m_uReference计数,不管怎样你都有权采用release或则retain的方式来改变对象的生命周期,这是你的自由!
(二) 无用对象与管理对象
Cocos2d-x 将会在帧过渡(当前帧和下一帧之间)自动清理无用的对象,什么是无用的对象?通过 create() 方法创建的就是无用的对象。
为了简要说明,代码的组织设计一切从简,我们创建了两个辅助类和一个容器类 BaseLayer,在 BaseLayer 之上管理内部对象,并观察它是怎么自动管理对象的。实现了其 构造函数 方法和 析构函数,并做些日志打印,以方便我们观察:
class LSLayer: public CCNode {
public:
virtual bool init() {
CCLog("LSLayer().init()");
return true;
}; CREATE_FUNC(LSLayer); LSLayer(){
CCLog("LSLayer().()");
};
~LSLayer() {
CCLog("LSLayer().~()");
};
}; class LSSprite: public CCNode {
public:
virtual bool init() {
CCLog("LSSprite().init()");
return true;
}; CREATE_FUNC(LSSprite); LSSprite(){
CCLog("LSSprite().()");
};
~LSSprite() {
CCLog("LSSprite().~()");
};
}; class BaseLayer: public CCLayer {
public:
virtual bool init(){
CCLog("BaseLayer().init()");
// 我们创建了两个 “无用”对象
LSLayer* layer = LSLayer::create();
LSSprite* sprite = LSSprite::create();
// 使 layer 变为受“管理”的对象
this->addChild(layer); return true;
}; CREATE_FUNC(BaseLayer); BaseLayer(){
CCLog("BaseLayer().()");
};
~BaseLayer(){
CCLog("BaseLayer().~()");
};
};
如上所示,我们在 BaseLayer 中创建了两个对象, layer 和 sprite,而只使用了 layer ,如果要运行上面的 BaseLayer 代码,我们需要创建一个 BaseLayer 的层对象,并将它添加到运行的场景或者层中: addChild(BaseLayer::create());,以保证 BaseLayer 开始运行,现在我们分析一下运行的结果:
// 由 addChild(BaseLayer::create()); 方法开始,创建并初始化了 BaseLayer 层
cocos2d-x debug info [BaseLayer().()]
cocos2d-x debug info [BaseLayer().init()]
// BaseLayer init 方法我们创建了两个对象
cocos2d-x debug info [LSLayer().()]
cocos2d-x debug info [LSLayer().init()]
cocos2d-x debug info [LSSprite().()]
cocos2d-x debug info [LSSprite().init()]
// 对象创建完成,紧接着这“无用”对象便已经释放了,而另一个已经使用的对象没有释放
cocos2d-x debug info [LSSprite().~()]
通过上面两个例子对比,对 cocos2d-x 的对象管理有了初步的认识,它会自动清理 “无用对象”。为了区分概念,我们将另一种对象称之为 “管理对象”,它是受管理的,有用的对象。比如上文中的 layer。
这也算初步认识,当然,这至少解决了我们这样一个疑问:我们在场景初始化的时候,通过 create() 创建了成员变量,以备需要的时候使用,但发现在使用的时候这个对象已经不存在了,从而导致程序崩溃。
(三) 管理对象不用之时立即回收
我们再继续演变 BaseLayer 的实现,以方便我们观察在每一帧对象的情况,添加实现了定时器功能:
class BaseLayer2: public CCLayer {
public:
virtual bool init(){
CCLog("BaseLayer2().init()");
// 启用定时器,自动在每一帧调用 update 方法
this->scheduleUpdate();
return true;
}; // 定义 update 统计
int updateCount;
LSLayer* layer;
LSSprite* sprite; virtual void update(float fDelta){
// 为了方便观察,不让 update 内部无止境的打印下去
if (updateCount < ){
updateCount ++;
CCLog("update index: %d", updateCount); // 在不同的帧做相关操作,以便观察
if (updateCount == ){
layer = LSLayer::create(); // autorelease 无用对象,下一帧之前被释放
this->addChild(layer);
sprite = LSSprite::create(); } else if (updateCount == ){
this->removeChild(layer, true); // 管理对象,立即释放 } else if (updateCount == ){ } CCLog("update index: %d end", updateCount);
}
}; CREATE_FUNC(BaseLayer2); BaseLayer2():
updateCount(),
layer(NULL),
sprite(NULL)
{
CCLog("BaseLayer2().()");
};
~BaseLayer2(){
CCLog("BaseLayer2().~()");
};
}; // 打印如下
cocos2d-x debug info [BaseLayer2().()]
cocos2d-x debug info [BaseLayer2().init()]
// 第一帧创建两个对象
cocos2d-x debug info [update index: ]
cocos2d-x debug info [LSLayer().()]
cocos2d-x debug info [LSLayer().init()]
cocos2d-x debug info [LSSprite().()]
cocos2d-x debug info [LSSprite().init()]
cocos2d-x debug info [update index: end]
// 我们看到 sprite 无用对象在 第一帧和第二帧之间被释放
cocos2d-x debug info [LSSprite().~()]
cocos2d-x debug info [update index: ]
// 在第二帧移除管理对象,可以看到它是立即释放,在 index: 2 end 之前
cocos2d-x debug info [LSLayer().~()]
cocos2d-x debug info [update index: end]
cocos2d-x debug info [update index: ]
cocos2d-x debug info [update index: end]
与无用对象不同的是(无用对象在 帧之间 被释放),管理对象在不用之时,立即释放,这决定着如果想在其它地方使用此对象,在“完全”不用之前,一定要有所作为。重写 update 方法如下:
virtual void update(float fDelta){
// 为了方便观察,不让 update 内部无止境的打印下去
if (updateCount < ){
updateCount ++;
CCLog("update index: %d", updateCount); // 在不同的帧做相关操作,以便观察
if (updateCount == ){
layer = LSLayer::create();
this->addChild(layer);
sprite = LSSprite::create();
CCLog("%d", layer);
} else if (updateCount == ){
// 在管理对象不用之前,一定要先有所动作
layer->retain();
this->removeChild(layer, true);
CCLog("%d", layer);
} else if (updateCount == ){
layer->release();
if (layer){
CCLog("%d", layer);
}
} CCLog("update index: %d end", updateCount);
}
}; /// 打印如下
cocos2d-x debug info [update index: ]
cocos2d-x debug info [LSLayer().()]
cocos2d-x debug info [LSLayer().init()]
cocos2d-x debug info [LSSprite().()]
cocos2d-x debug info [LSSprite().init()]
cocos2d-x debug info []
cocos2d-x debug info [update index: end]
cocos2d-x debug info [LSSprite().~()]
cocos2d-x debug info [update index: ]
// 第二帧并没有释放 layer,因为它还是有用的管理对象
cocos2d-x debug info []
cocos2d-x debug info [update index: end]
cocos2d-x debug info [update index: ]
// 完全弃用,立即释放
cocos2d-x debug info [LSLayer().~()]
// 但是 layer 对象的地址还是可用的
cocos2d-x debug info []
cocos2d-x debug info [update index: end]
在完全不用之前,要有所作为。 如果我们将第二帧中的 layer->retain(); 放在 this->removeChild(layer, true); 之后呢,我们知道在 removeChild 之后是立即释放的,此时 layer 对象已经不存在了,而 layer 所指向的内存地址是个无效地址。如果你的程序继续运行,那么一定会出现内存错误。
如果程序直接错误异常退出,倒也罢了,怕就怕,程序可能继续运行,layer 虽然是无效地址(野指针),但并不是 NULL,可能所指向的地址可用,可能还能继续执行,更可能的还能继续 layer->retain(); 操作。这会影响我们的判断,程序真的有问题么。如果留下了这种隐患,那么排除错误的难度会大大加深。比如程序莫名其妙的退出,时好时坏!
第三帧我们通过 if (layer) 判断对象是否可用,如果可用我们继续操作 layer ,这样的使用方式也将会留下内存隐患,因为这样的判断是能通过的,但却是不一定能够正确使用的。
一般而言,我们不一定需要 if(layer) 诸如此类的判断,这也是不推荐的。管理对象,谁使用,那么谁就是可控的!如果在对象销毁之前谁 retain() ,那么在release() 之前,它无需判断即可使用。谁 addXXX 使用,一般能通过 getXXX 获取。
简而言之,谁使用(引用),你就找谁就行了,不论是获取,或者移除。
我们前面所言,管理对象不用之时,立即回收,那么我们在同一帧使用,然后移除呢?我们继续改写 update 方法,验证想法:
virtual void update(float fDelta){
// 为了方便观察,不让 update 内部无止境的打印下去
if (updateCount < ){
updateCount ++;
CCLog("update index: %d", updateCount); // 在不同的帧做相关操作,以便观察
if (updateCount == ){
layer = LSLayer::create();
this->addChild(layer);
this->removeChild(layer, true);
} else if (updateCount == ){ } else if (updateCount == ){ } CCLog("update index: %d end", updateCount);
}
}; /// 其打印如下
cocos2d-x debug info [update index: ]
cocos2d-x debug info [LSLayer().()]
cocos2d-x debug info [LSLayer().init()]
cocos2d-x debug info [update index: end]
// layer 在两帧之间释放,也既是在下一帧自动清理
cocos2d-x debug info [LSLayer().~()]
cocos2d-x debug info [update index: ]
cocos2d-x debug info [update index: end]
这里我们在同一帧 addChild 并且随之 removeChild,那么 layer 的性质又是如何,我们知道 管理对象在不用之时会立即释放,但在这里并没有立即释放,那说明什么,说明 layer 并不是管理对象,还只是无用对象,并且在这一帧结束时,或者说在 帧过度 的时候,并没有使用,所以在帧之间被清理. 可想而知,(注意注意)在 帧过度 的时候,其内部做了些处理 : 自动清理无用对象,或者将已使用的无用对象变成管理对象,而在以后的帧,如果管理对象不再被引用,将会立即释放。
注: 通过上面代码分析可以知道, 在帧过渡的时候,不仅仅会清理无用对象, 同时无用对象变成管理对象也是在这时候发生,也即, 比如当我们执行"this->addChild(layer);"的时候,无用对象不会马上变成管理对象,而是在帧过渡期间才执行转变.
现在来看一看稍微复杂点的结构会如何。
// 在不同的帧做相关操作,以便观察
if (updateCount == ){
layer = LSLayer::create();
sprite = LSSprite::create();
layer->addChild(sprite);
addChild(layer);
} else if (updateCount == ){
this->removeChild(layer, true);
} else if (updateCount == ){
} /// 打印如下
cocos2d-x debug info [update index: ]
cocos2d-x debug info [LSLayer().~()]
cocos2d-x debug info [LSSprite().~()]
cocos2d-x debug info [update index: end]
我们创建了两个对象 layer 和 sprite,将 sprite 添加到 layer,并把通过 addChild(layer) 使用 layer,可以看到,在第二帧移除 layer 的时候,立即释放了 layer 和 sprite 对象。这也是 cocos2d-x 自动管理所实现的功能,在 使用者(父对象) 不用的时候,它也将会解除对 其它对象(子对象)的使用。
基于以上情况,做些变形:
// 在不同的帧做相关操作,以便观察
if (updateCount == ){
layer = LSLayer::create();
sprite = LSSprite::create();
layer->addChild(sprite);
} else if (updateCount == ){
} else if (updateCount == ){
} /// 打印如下
cocos2d-x debug info [update index: end]
cocos2d-x debug info [LSLayer().~()]
cocos2d-x debug info [LSSprite().~()]
cocos2d-x debug info [update index: ]
cocos2d-x debug info [update index: end]
创建了两个对象 layer 和 sprite,将 sprite 添加到 layer 之中,而对 layer 不做处理,我们知道 layer 在第一帧结束后,会自动释放,所以也会释放其所引用的 sprite,而此时 sprite 的性质就有点微妙了。它在帧过度之间是怎么处理的,它是不是我们这所说的无用对象呢?哈!如果 layer 首先被自动管理,那么它会首先回收,并取消对 sprite 的引用,那么 sprite 就是个无用对象,被自动回收。如果 sprite 首先被自动管理,那么它将会先变成一个管理对象,然后在 layer 自动释放并取消对 sprite 引用的时候,被立即释放。从效果上来说,都是一帧之内完成的。
---------------------------------------------------------------------------------------
首先需要确定的一点是,cocos2dx采用的是引用计数的方式来管理对象的持有和释放。所谓引用计数就是说,每个对象都会有一个属性用来记录当前被几个地方引用了。在释放内存的时候会根据这个引用计数来确定是否要用delete操作符来释放这个对象占用的内存。具体见CCObeject的默认构造函数,retain和release方法。
然后再看autorelease这个方法,当一个对象被加到CCPoolManager里面以后这个对象的引用计数还是1(如果没有别的地方调用它的retain方法的话)。我们知道,在CCDirect的主循环方法mainLoop里面会调用CCPoolManager的pop方法,pop方法的作用其实只有一个,把当前持有的对象的引用释放,同时调用每个对象release方法(有可能被释放也有可能不释放,这取决于引用计数)。pop方法被调用以后,之前一次通过autorelease方法加到CCPoolManager中的所有对象的死活CCPoolManager都不再管了(已经放弃对这些对象的引用了)。也就是说,这个所谓的autorelease方法其实只是一个一锤子买卖。
通过上面的分析可以总结出一个使用cocos2dx内存管理机制的一个正确规则,retain或new操作符和release必须成对出现,哪里构造的时候调了某个对象的retain方法,那在他的析构的时候就一定要调该对象的release方法。
比如我们在写代码的时候写的最多的就是把一个spriter加到layer中,实际上是加到了ccnode的一个数组中,在加入这个数组时会调用对象的retain方法,在移除这个对象时也会调用它的release方法。而在析构函数的时候layer也会调用这个数组的release方法,数组又调用它持有的所以的对象的release方法。
总的来讲,整个机制的实现还是很简单的,但是auto这种字眼很迷惑人,并不是正在的auto。所以在写代码的时候还是应该小心。
autoRelease主要是用来管理在方法作用域内通过new创建的对象的释放的,以达到这种类型的对象的内存释放能像普通定义的对象一样在方法调用一结束就进行释放。
cocos2d-x游戏引擎核心之二——内存管理的更多相关文章
- cocos2d-x游戏引擎核心(3.x)----事件分发机制之事件从(android,ios,desktop)系统传到cocos2dx的过程浅析
(一) Android平台下: cocos2dx 版本3.2,先导入一个android工程,然后看下AndroidManifest.xml <application android:label= ...
- cocos2d-x游戏引擎核心之十一——并发编程(消息通知中心)
[续] cocos2d-x游戏引擎核心之八——多线程 这里介绍cocos2d-x的一种消息/数据传递方式,内置的观察者模式,也称消息通知中心,CCNotificationCenter. 虽然引擎没有为 ...
- cocos2d-x游戏引擎核心之六——绘图原理和绘图技巧
一.OpenGL基础 游戏引擎是对底层绘图接口的包装,Cocos2d-x 也一样,它是对不同平台下 OpenGL 的包装.OpenGL 全称为 Open Graphics Library,是一个开放的 ...
- Unity移动游戏加载性能和内存管理-学习笔记
前言 正在学习Doctor 张.鑫大佬的移动游戏加载性能和内存管理,内容非常非常的干,所以我烧了很多开水,边喝边看,一边拿小本几做好笔记 本文只是关于前2章的内容笔记,关于各种资源的加载耗时 纹理资源 ...
- Android笔记--Bitmap(二)内存管理
Bitmap(二) 内存管理 1.使用内存缓存保证流畅性 这种使用方式在ListView等这种滚动条的展示方式中使用最为广泛, 使用内存缓存 内存缓存位图可以提供最快的展示.但代价就是占用一定的内存空 ...
- cocos2d-x游戏引擎核心之八——多线程
一.多线程原理 (1)单线程的尴尬 重新回顾下 Cocos2d-x 的并行机制.引擎内部实现了一个庞大的主循环,在每帧之间更新各个精灵的状态.执行动作.调用定时函数等,这些操作之间可以保证严格独立,互 ...
- cocos2d-x游戏引擎核心之九——跨平台
一.cocos2d-x跨平台 cocos2d-x到底是怎样实现跨平台的呢?这里以Win32和Android为例. 1. 跨平台项目目录结构 先看一下一个项目创建后的目录结构吧!这还是以HelloCpp ...
- cocos2d-x游戏引擎核心之三——主循环和定时器
一.游戏主循环 在介绍游戏基本概念的时候,我们曾介绍了场景.层.精灵等游戏元素,但我们却故意避开了另一个同样重要的概念,那就是游戏主循环,这是因为 Cocos2d 已经为我们隐藏了游戏主循环的实现.读 ...
- cocos2d-x游戏引擎核心(3.x)----启动渲染流程
(1) 首先,这里以win32平台下为例子.win32下游戏的启动都是从win32目录下main文件开始的,即是游戏的入口函数,如下: #include "main.h" #inc ...
随机推荐
- http://blog.csdn.net/ouyang_peng/article/details/8732644
http://blog.csdn.net/ouyang_peng/article/details/8732644
- Tslib步骤以及出现问题的解决方案(转)
嵌入式设备中触摸屏使用非常广泛,但触摸屏的坐标和屏的坐标是不对称的,需要校准.校准广泛使用的是开源的tslib. Tslib是一个开源的程序,能够为触摸屏驱动获得的采样提供诸如滤波.去抖.校准等功能, ...
- 错误将UIViewController当做UITableViewController来用
- Entity Framework应用:使用Code First模式管理视图
一.什么是视图 视图在RDBMS(关系型数据库管理系统)中扮演了一个重要的角色,它是将多个表的数据联结成一种看起来像是一张表的结构,但是没有提供持久化.因此,可以将视图看成是一个原生表数据顶层的一个抽 ...
- Entity Framework管理实体关系(二):管理一对二关系
在上一篇文章中,简单的介绍了使用Fluent API如何管理一对一的实体关系,在这篇文章中,接着介绍Fluent API如何管理一对多的实体关系. 要在数据库中配置一对多关系,我们可以依赖EF约定,还 ...
- C#里面的三种定时计时器:Timer
在.NET中有三种计时器:1.System.Windows.Forms命名空间下的Timer控件,它直接继承自Componet.Timer控件只有绑定了Tick事件和设置Enabled=True后才会 ...
- 二、Linux 静态IP,动态IP配置
Linux 静态IP,动态IP配置 第一步:激活网卡 系统装好后默认的网卡是eth0,用下面的命令将这块网卡激活. # ifconfig eth0 up 第二步:设置网卡进入系统时启动 想要每次开机就 ...
- StarRTC , AndroidThings , 树莓派小车,公网环境,视频遥控(一)准备工作
原文地址:http://blog.starrtc.com/?p=48 啥也不说,先来个视频看看效果 视频播放器 00:00 00:54 概述为了体现StarRTC的实时音视频传输能 ...
- 实现整数转化为字符串函数itoa()函数
函数原型: char *itoa( int value, char *string,int radix);原型说明:value:欲转换的数据.string:目标字符串的地址.radix:转换后的进制数 ...
- 消息中间件系列之ActiveMQ的简单安装
本次测试使用一台ip为192.168.2.12的虚拟机 一.解压压缩包 tar -zxvf apache-activemq-5.14.4-bin.tar.gz 二.启动activemq 进入到bin目 ...