【基础】Cocos2d-x 内存管理机制
2015-06-12 15:17
405 查看
【声明】
本文版权归果冻想所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。
如果这篇文章对你有帮助,你可以请我喝杯咖啡。
» 本文链接:http://www.jellythink.com/archives/776
» 订阅本站:http://www.jellythink.com/feed
» 转载请注明来源:果冻想 » <a rel="bookmark" title="菜鸟学习Cocos2d-x 3.x——内存管理"《菜鸟学习Cocos2d-x
3.x——内存管理》
原文链接:http://www.jellythink.com/archives/776
【正文】
到现在,内存已经非常便宜,但是也不是可以无限大的让你去使用,特别是在移动端,那么点内存,那么多 APP要抢着用,搞不好,你占的内存太多了,系统直接干掉你的APP,所以说了,我们又要老生常谈了——内存管理。总结COM开发的时候,分析过COM的 内存管理模式;总结Lua的时候,也分析了Lua的内存回收机制;前几天,还专门写了C++中的智能指针在内存使用方面的应用;可见,内存管理无论是语言 层面,还是类库层面,都有严格的标准和实施,对于Cocos2d-x来说,也是如此。那么在Cocos2d-x中,它是如何进行内存管理的呢?这篇文章,
我就来总结一下关于Cocos2d-x的内存管理方面的知识。让你轻松度过面试官的五指关(面试Cocos2d-x时,100%会问到的问题啊,上点心 吧)。
对于探究内存管理这种比较抽象的东西,最简单的方法就是通过代码来研究,首先通过创建一个简单的场景来看看Cocos2d-x在完成创建一个对象的时候,它都干了些什么。
创建一个Scene:
现在就涉及到了Cocos2d-x的内存管理相关的知识了。在Cocos2d-x中,关于对象的创建与初始化都是使用的new和
由于这种方式在Cocos2d-x中经常被使用,所以触控那帮家伙就搞了个宏:
话说这些东西也都是基础的C++知识,没有多少需要说的了,当你看到代码中的
在Cocos2d-x中,关于内存管理的类有:
Ref类;
AutoreleasePool类;
PoolManager类。
Ref类几乎是Cocos2d-x中所有类的父类,它是Cocos2d-x中内存管理的最重要的一环;上面说的
AutoreleasePool类用来管理自动释放对象。
PoolManager用来管理所有的AutoreleasePool,这个类是使用单例模式实现的。
下面就通过对上述三个类的源码进行分析,看看Cocos2d-x到底是如何进行内存管理的。
先来看看Ref类的定义,以下是Ref类的头文件定义:
要匹配使用,否则就会出现断言错误,或者内存泄露;在非Debug模式下,就可能直接闪退了。这就是为什么我们在使用create函数的时候,new成功 以后,就顺便调用了autorelease,将该对象放入到自动释放池中;而当我们再次想获取该对象并使用该对象的时候,需要使用retain再次获得该 对象的所有权,当然了,在使用完成以后,你应该记得调用release去手动完成释放工作,这是你的任务。例如以下代码:
pool中了;当你再次放入autorelease pool后,当销毁autorelease pool以后,就会出现两次销毁一个对象的情况,出现程序的crash。再例如以下代码也是错误的:
这引用计数,又让我想起了COM中的AddRef和Release。
AutoreleasePool类是Ref类的友元类,先来看看Autorelease类的声明。
对于AutoreleasePool类来说,它的实现很简单,就是将简单的将对象保存在一个std::vector中,在释放这个AutoreleasePool的时候,对保存在std::vector中的对象依次调用对应的release函数,从而完成对象的自动释放。
这货又是干什么的?当我们在阅读AutoreleasePool的源码的时候,在它的构造函数中,你会发现如下代码:
来,PoolManager类就是用来管理所有的AutoreleasePool的类,也是使用的单例模式来实现的。该PoolManger有一个存放 AutoreleasePool对象指针的stack,该stack是由std::vector实现的。需要注意的是,cocos2d-x的单例类都不是 线程安全的,跟内存管理紧密相关的PoolManager类也不例外,因此在多线程中使用cocos2d-x的接口需要特别注意内存管理的问题。关于更安 全的单例模式,感兴趣的同学可以去阅读这篇《C++设计模式——单例模式》。接下来,我们先看看PoolManager的头文件定义。
关于PoolManager中各个函数的实现也是非常简单的,这里不做累述,各位可以去阅读Cocos2d-x的源码。
说了这么多,代码也列了这么多,我们create一个对象以后,放到了 AutoreleasePool中去了,最终,在调用AutoreleasePool的clear函数的时候,会对AutoreleasePool管理的 所有对象依次调用release操作。啊哈!貌似哪里不对,我一直都没有说最终谁会调用这个clear函数啊?是的。看下面这段在导演类中的代码,我想你 会明白的。
上面的代码说明的事实是:在图像渲染的主循环中,如果当前的图形对象是在当前帧,则调用显示函数,并 调用AutoreleasePool::clear()减少这些对象的引用计数。mainLoop是每一帧都会自动调用的,所以下一帧时这些对象都被当前 的AutoreleasePool对象release了一次。这也是AutoreleasePool「自动」的来由。
好了,总结的差不多了,对于Cocos2d-x中的内存管理总结的差不多了。对于Cocos2d-x 中的内存管理,我个人认为,请时刻关注着这个对象的引用计数,retain和release,new和autorelease需要匹配使用,防止不必要的 错误发生。总结了这么多,还是那句话。
纸上得来终觉浅,绝知此事要躬行。
只有经过实际的使用,在经过代码的洗练,才能更好的去掌握这些。在Cocos2d-x中,很多地方已 经进行了autorelease,或者retain了,我们就不必再次进行这些操作,比如create,再比如在调用addChild方法添加子节点时, 自动调用了retain。对应的通过removeChild,移除子节点时,自动调用了release。这些地方稍微不注意,就可能会让你掉入“坑”中。 努力吧,伙计们。
【附录】
原文地址:http://www.jellythink.com/archives/776
本文版权归果冻想所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。
如果这篇文章对你有帮助,你可以请我喝杯咖啡。
» 本文链接:http://www.jellythink.com/archives/776
» 订阅本站:http://www.jellythink.com/feed
» 转载请注明来源:果冻想 » <a rel="bookmark" title="菜鸟学习Cocos2d-x 3.x——内存管理"《菜鸟学习Cocos2d-x
3.x——内存管理》
原文链接:http://www.jellythink.com/archives/776
【正文】
亘古不变的东西
到现在,内存已经非常便宜,但是也不是可以无限大的让你去使用,特别是在移动端,那么点内存,那么多 APP要抢着用,搞不好,你占的内存太多了,系统直接干掉你的APP,所以说了,我们又要老生常谈了——内存管理。总结COM开发的时候,分析过COM的 内存管理模式;总结Lua的时候,也分析了Lua的内存回收机制;前几天,还专门写了C++中的智能指针在内存使用方面的应用;可见,内存管理无论是语言 层面,还是类库层面,都有严格的标准和实施,对于Cocos2d-x来说,也是如此。那么在Cocos2d-x中,它是如何进行内存管理的呢?这篇文章,我就来总结一下关于Cocos2d-x的内存管理方面的知识。让你轻松度过面试官的五指关(面试Cocos2d-x时,100%会问到的问题啊,上点心 吧)。
初窥Cocos2d-x内存管理
对于探究内存管理这种比较抽象的东西,最简单的方法就是通过代码来研究,首先通过创建一个简单的场景来看看Cocos2d-x在完成创建一个对象的时候,它都干了些什么。创建一个Scene:
auto scene = Scene::create();函数
create是一个静态函数,看看
create函数的源码:
Scene *Scene::create() { Scene *ret = new Scene(); if (ret && ret->init()) { ret->autorelease(); return ret; } else { CC_SAFE_DELETE(ret); return nullptr; } }
现在就涉及到了Cocos2d-x的内存管理相关的知识了。在Cocos2d-x中,关于对象的创建与初始化都是使用的new和
init函数搭配的方式,这种方式叫做二段式创建,由于C++中,构造函数没有返回值,无法通过构造函数确定初始化的成功与失败,所以在Cocos2d-x中就大行其道的使用了这种二段式创建的方式,用起来还不错,以后在自己的项目中,也可以采用。
由于这种方式在Cocos2d-x中经常被使用,所以触控那帮家伙就搞了个宏:
CREATE_FUNC。如果想让我们的类也使用这种二段式创建的方式,只需要在我们的类中加入以下代码:
CREATE_FUNC(classname);同时,需要定义一个init函数,这就OK了。我们来看看这个宏:
#define CREATE_FUNC(__TYPE__) \ static __TYPE__* create() \ { \ __TYPE__ *pRet = new __TYPE__(); \ if (pRet && pRet->init()) \ { \ pRet->autorelease(); \ return pRet; \ } \ else \ { \ delete pRet; \ pRet = NULL; \ return NULL; \ } \ }
话说这些东西也都是基础的C++知识,没有多少需要说的了,当你看到代码中的
ret->autorelease(),一脸茫然,是的,你已经看到了Cocos2d-x的内存管理的触角了。
ret->autorelease()是什么?当我使用create函数创建了场景以后,我并没有去delete,这也没有问题。问题就发生在这个
autorelease的使用上。序幕说完了,让我们真正的开始Cocos2d-x的内存管理吧。
在Cocos2d-x中,关于内存管理的类有:
Ref类;
AutoreleasePool类;
PoolManager类。
Ref类几乎是Cocos2d-x中所有类的父类,它是Cocos2d-x中内存管理的最重要的一环;上面说的
autorelease函数就Ref类的成员函数,Cocos2d-x中所有继承自Ref的类,都可以使用Cocos2d-x的内存管理。
AutoreleasePool类用来管理自动释放对象。
PoolManager用来管理所有的AutoreleasePool,这个类是使用单例模式实现的。
下面就通过对上述三个类的源码进行分析,看看Cocos2d-x到底是如何进行内存管理的。
Ref类
先来看看Ref类的定义,以下是Ref类的头文件定义:class CC_DLL Ref { public: /** * 获取对象的所有权 * 增加对象的引用计数 */ void retain(); /** * 立即释放对象的所有权 * 同时会减少对象的引用计数,当引用计数达到0时,直接销毁这个对象 */ void release(); /** * 自动释放对象的所有权 * 将对象添加到自动释放池 * 当在下一帧开始前,当前的自动释放池会被回收掉,并且对自动释放池中的所有对象 * 执行一次release操作,当对象的引用计数为0时,对象会被释放掉。 */ Ref* autorelease(); /** * 获得对象的当前引用计数 * 当创建对象的时候,引用计数为1 */ unsigned int getReferenceCount() const; };对于
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)) { // 这里是非常重要的一点,在我们使用Cocos2d-x中经常出错的地方 // 当引用计数为0,同时这个对象还存在于autorelease池中的时候,就会出现一个断言错误 // 可以想到,当这个对象引用计数为0时,就表示需要释放掉,如果它还在autorelease池中, // 当在autorelease池中再次被释放时,就会出现错误,这种错误是不了解Cocos2d-x内存管理的 // 编程人员经常犯的错误。 // // 出现这个错误的原因在于new/retain和autorelease/release没有对应使用引起的 CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool."); } #endif delete this; } }上面也说道了,对于new和autorelease需要匹配使用,retain和release也需
要匹配使用,否则就会出现断言错误,或者内存泄露;在非Debug模式下,就可能直接闪退了。这就是为什么我们在使用create函数的时候,new成功 以后,就顺便调用了autorelease,将该对象放入到自动释放池中;而当我们再次想获取该对象并使用该对象的时候,需要使用retain再次获得该 对象的所有权,当然了,在使用完成以后,你应该记得调用release去手动完成释放工作,这是你的任务。例如以下代码:
auto obj = Scene::create(); obj->autorelease(); // Error这是错误的,在create中,在创建成功的情况下,已经将obj对象放到了autorelease
pool中了;当你再次放入autorelease pool后,当销毁autorelease pool以后,就会出现两次销毁一个对象的情况,出现程序的crash。再例如以下代码也是错误的:
auto obj = Scene::create(); obj->release(); // Error当使用create函数创建对象以后,obj没有所有权,当再次调用release时,就会出现错误的对象释放。而正确的做法应该如下:
auto obj = Scene::create(); // 这里retain和release对应,release一个已经被autorelease过的对象(例如通过create函数构造的对象)必须先retain obj->retain(); obj->release();
这引用计数,又让我想起了COM中的AddRef和Release。
AutoreleasePool类
AutoreleasePool类是Ref类的友元类,先来看看Autorelease类的声明。class CC_DLL AutoreleasePool { public: /** * 不能在堆上创建AutoreleasePool对象,只能在栈上创建 * 这就决定过了,当出了对应的作用域,AutoreleasePool对象就会被自动释放,例如RAII技巧实现的 */ AutoreleasePool(); /** * 创建一个带有指定名字的autorelease pool对象 * 对于调试来说,这个名字是非常有用的。 */ AutoreleasePool(const std::string &name); ~AutoreleasePool(); /** * 向autorelease pool中添加一个ref对象 * 同一个对象可以多次加入同一个自动释放池中(貌似会触发断言错误) * 当自动释放池被销毁的时候,它会依次调用自动释放池中对象的release()函数 */ void addObject(Ref *object); /** * 清理自动释放池 * 依次调用自动释放池中对象的release()函数 */ void clear(); #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) /** * 判断当前是否正在执行自动释放池的清理操作 */ bool isClearing() const { return _isClearing; }; #endif /** * 判断自动释放池是否包含指定的Ref对象 */ bool contains(Ref* object) const; /** * 打印autorelease pool中所有的对象 */ void dump(); private: /** * 所有的对象都是使用的std::vector来存放的 */ std::vector<Ref*> _managedObjectArray; std::string _name; #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) /** * The flag for checking whether the pool is doing `clear` operation. */ bool _isClearing; #endif };
对于AutoreleasePool类来说,它的实现很简单,就是将简单的将对象保存在一个std::vector中,在释放这个AutoreleasePool的时候,对保存在std::vector中的对象依次调用对应的release函数,从而完成对象的自动释放。
PoolManager类
这货又是干什么的?当我们在阅读AutoreleasePool的源码的时候,在它的构造函数中,你会发现如下代码:AutoreleasePool::AutoreleasePool() : _name("") #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) , _isClearing(false) #endif { _managedObjectArray.reserve(150); PoolManager::getInstance()->push(this); }在AutoreleasePool的析构函数中,又有如下代码:
AutoreleasePool::~AutoreleasePool() { CCLOGINFO("deallocing AutoreleasePool: %p", this); clear(); PoolManager::getInstance()->pop(); }哦,原来,我们把AutoreleasePool对象又放到了PoolManager里了;原
来,PoolManager类就是用来管理所有的AutoreleasePool的类,也是使用的单例模式来实现的。该PoolManger有一个存放 AutoreleasePool对象指针的stack,该stack是由std::vector实现的。需要注意的是,cocos2d-x的单例类都不是 线程安全的,跟内存管理紧密相关的PoolManager类也不例外,因此在多线程中使用cocos2d-x的接口需要特别注意内存管理的问题。关于更安 全的单例模式,感兴趣的同学可以去阅读这篇《C++设计模式——单例模式》。接下来,我们先看看PoolManager的头文件定义。
class CC_DLL PoolManager { public: /** * 获得单例 */ static PoolManager* getInstance(); /** * 销毁单例 */ static void destroyInstance(); /** * 获得当前的autorelease pool,在引擎中,至少会有一个autorelease pool * 在需要的时候,我们可以创建我们自己的release pool,然后将这个autorelease pool添加到PoolManager中 */ AutoreleasePool *getCurrentPool() const; /** * 判断指定的对象是否在其中的一个autorelease pool中 */ bool isObjectInPools(Ref* obj) const; friend class AutoreleasePool; private: PoolManager(); ~PoolManager(); /** * 将AutoreleasePool对象添加到PoolManager中 */ void push(AutoreleasePool *pool); /** * 从PoolManager中移除AutoreleasePool对象 */ void pop(); static PoolManager* s_singleInstance; /** * 用来保存所有的AutoreleasePool对象 */ std::deque<AutoreleasePool*> _releasePoolStack; AutoreleasePool *_curReleasePool; };
关于PoolManager中各个函数的实现也是非常简单的,这里不做累述,各位可以去阅读Cocos2d-x的源码。
问题来了
说了这么多,代码也列了这么多,我们create一个对象以后,放到了 AutoreleasePool中去了,最终,在调用AutoreleasePool的clear函数的时候,会对AutoreleasePool管理的 所有对象依次调用release操作。啊哈!貌似哪里不对,我一直都没有说最终谁会调用这个clear函数啊?是的。看下面这段在导演类中的代码,我想你 会明白的。void DisplayLinkDirector::mainLoop() { if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (! _invalid) { drawScene(); // release the objects PoolManager::getInstance()->getCurrentPool()->clear(); } }
上面的代码说明的事实是:在图像渲染的主循环中,如果当前的图形对象是在当前帧,则调用显示函数,并 调用AutoreleasePool::clear()减少这些对象的引用计数。mainLoop是每一帧都会自动调用的,所以下一帧时这些对象都被当前 的AutoreleasePool对象release了一次。这也是AutoreleasePool「自动」的来由。
总结
好了,总结的差不多了,对于Cocos2d-x中的内存管理总结的差不多了。对于Cocos2d-x 中的内存管理,我个人认为,请时刻关注着这个对象的引用计数,retain和release,new和autorelease需要匹配使用,防止不必要的 错误发生。总结了这么多,还是那句话。纸上得来终觉浅,绝知此事要躬行。
只有经过实际的使用,在经过代码的洗练,才能更好的去掌握这些。在Cocos2d-x中,很多地方已 经进行了autorelease,或者retain了,我们就不必再次进行这些操作,比如create,再比如在调用addChild方法添加子节点时, 自动调用了retain。对应的通过removeChild,移除子节点时,自动调用了release。这些地方稍微不注意,就可能会让你掉入“坑”中。 努力吧,伙计们。
【附录】
原文地址:http://www.jellythink.com/archives/776
相关文章推荐
- cocos2dx: win32程序Release和debug版本:lua:print函数
- 【基础】Cocos2d-x 浅谈事件处理机制
- cocos2d-x3.0基础数学知识(为防伸手党,复制粘贴,以图片为主,还是一字一码的敲下吧^_^!)
- cocos2dx之CocosWidget项目介绍
- cocos2d-x 3.2 android返回键退出
- cocos2dx 两个重叠按钮点击响应关系
- cocos2d-x 设置屏幕方向 横屏 || 竖屏
- cocos2dx命令行编译打包安卓apk ndk_root not defined
- cocos2d-x CCScrollView和CCTableView的使用
- 编译打包安卓apk报错:软件包 org.cocos2dx.lib 不存在
- Cocos2d-X手游源码/iOS/Android/cocos2dx源码/AppStore/手游资源“集中营”
- cocos2d-js添加百度appx的插屏广告(通过jsb反射机制)
- (1)cocos2d-x-2.2.4搭建windows开发环境
- cocos3.3 响应Android的Menu键和Back键
- quick-cocos2dx使用windows下向导生成的IOS工程在MAC下编译报错的解决方法
- cocos2dx 3.x 内存管理总结
- 【支付】Cocos2d-x IOS内购(IAP支付)
- cocos2d-x中shader的使用
- cocos2dx学习
- quick-cocos2d-x3.3在windows下编译release版报错的解决方法