Cocos2d-x 3.x内存管理机制
1 目的
虽然目前手机的内存容量相比较以前有了大幅度的提升,但是面对众多游戏应用等APP,手机内存再大也是不够的,对于安卓机来说,内存占了太多就会被卡死或者会造成软件闪退等清理,于是才有了下面要和大家介绍的Cocos2d-x内存管理机制,帮助大家去优化游戏资源,减少内存占比。
2 引用计数的内存管理机制
Cocos2d-x采用引用计数管理内存,简单的说:在对象内部添加一个计数器,当外部引用增加时,引用计数加1,反之,外部引用计数消失时,引用计数减1,直到该对象的引用计数为0时,引擎会删除该对象。在Cocos2d-x中,关于内存管理的类有:
1. Ref;
2. AutoreleasePool;
3. PoolManager.
2.1 Ref
类Ref几乎是Cocos2d-x中所有类的父类,Ref类提供了引用计数的功能。下面是Cocos2d-x3.x中Ref类头文件的定义:
class CC_DLL Ref { public: /** * 获取对象的所有权 * 增加对象的引用计数 */ void retain(); /** * 立即释放对象的所有权 * 减少对象的引用计数,当对象引用计数减为0时,直接销毁对象 */ void release(); /** * 自动释放对象的所有权 * 将对象添加到自动释放池,并且减少对象的引用计数,当对象引用计数减为0时,直接销毁对象 */ Ref* autorelease(); /** * 获取当前对象的引用计数 * 对象被创建时引用计数为1(构造函数初始化_referenceCount为1) */ unsigned int getReferenceCount() const; protected: Ref(); public: virtual ~Ref(); protected: unsigned int _referenceCount; // 引用计数 friend class AutoreleasePool; public: unsigned int _ID; int _luaID; };
这里尤其要说明release方法,该方法源码如下:
void Ref::release() { CCASSERT(_referenceCount > 0, "reference count should be greaterthan 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 autoreleasepool."); } #endif } }
上面提到了,对于new和autorelease、retain和release需要匹配使用,否则就会出现断言错误,或者内存泄露,这非常类似于C++中new和delete的成对使用;在非Debug模式下,就可能直接闪退了。这就是为什么我们在使用create函数的时候,new成功以后,就顺便调用了autorelease,将该对象放入到自动释放池中;而当我们再次想获取该对象并使用该对象的时候,需要使用retain再次获得对象的所有权,当然在使用完成以后,应该记得调用release去手动完成释放工作。下面是Cocos2d-x中常见创建autorelease对象的方法:
Wrong usage (1):重复加入自动对象池
auto obj= Node::create();// 引用计数为1,autorelease对象,已经被加入自动释放池
obj->autorelease();//WRONG!!! 企图再次将obj加入自动释放池。Ifyou wish to invoke autorelease several times, you should retain `obj` first.
Wrong usage (2):重复释放对象
autoobj = Node::create();
obj->release(); // WRONG!!! obj is an autorelease Ref, itwill be released when clearing current pool.
Correct usage(1): 成对使用new和autorelease
autoobj = 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`。
Correct usage(2): 成对使用retain和release
autoobj = Node::create();
obj->retain();
obj->release(); // This `release` is the pair of `retain` ofprevious line.
请记住:利用Cocos2d-x引擎的引用计数管理机制,我们可以不再向C++由程序员手动分配和释放内存。在Cocos2d-x中最常见的应用是:通过在类中加入CREATE_FUNC(className);来创建对象使代码简洁优雅,且内存管理得当。下面是CREATE_FUNC(className)宏的定义:
#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; \ }\ }
在这里,new和autorelease成对调用,且调用了HelloWorldScene中的init方法。
在类中只需要加入一行代码CREATE_FUNC(className);即可让我们创建一个autorelease对象(自动释放对象)如此简单。
auto sprite = Sprite::create(“1.png”); 或者像这样
auto layer = HelloWorldScene::create();
上述代码不仅简洁优雅,而且维护的是一个自动释放的对象,降低开发复杂度,减少程序员手动管理内存出错的机会。
2.2 AutoreleasePool
Cocos2d-x还使用了AutoreleasePool(自动释放池)来管理内存。需要自动释放的对象通过autorelease()方法将其加入自动释放池中,然后自动释放池AutoreleasePool帮我们进行内存管理。其中,在CREATE_FUNC宏中,使用new动态分配一块内存并调用类构造函数创建了对象,然后又成对的使用了autorelease()方法把创建的对象放入自动释放池,这使得我们可以使用create方法就可以简洁的创建对象,而不需要像C++使用原始的方法(new和delete)创建对象和手动释放对象。下面是AutoreleasePool类头文件源码:
class CC_DLL AutoreleasePool { public: /** * 不能在堆上创建一个自动释放对象,在栈上创建 * 在栈上创建自动释放对象决定了该对象会被自动释放 */ AutoreleasePool(); /** * 创建一个带有指定名字的autorelease pool对象,这个名字对调试非常有用 */ AutoreleasePool(const std::string &name); ~AutoreleasePool(); /** * 将指定的`自动释放对象`加入自动释放池 * 同一对象可能加入到自动释放池中很多次(不过会断言错误啊) * 当自动释放池销毁时,它会调用同样次数的`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容器,它是用于存放自动对象的数据结构 */ std::vector<Ref*> _managedObjectArray; std::string _name; #if defined(COCOS2D_DEBUG) &&(COCOS2D_DEBUG > 0) /** * Theflag for checking whether the pool is doing `clear` operation. */ bool _isClearing; #endif };
AutoreleasePool内部使用std::vector容器作为存储自动释放对象的数据结构。类中的实现很简单,就是将对象保存子啊std::vector中,释放AutoreleasePool时依次调用对应的release函数释放保存在std::vector中的对象,从而完成对象的自动释放。
2.3 PoolManager
既然有了AutoreleasePool来管理对象的自动释放,那么PoolManager是做什么的?看其字面意思是`池管理器`。观查AutoreleasePool的源码后发现,其构造和析构函数都使用了PoolManager,原来是把AutoreleasePool对象都放于PoolManager管理,并且该类为单例类。先看看AutoreleasePool中的构造和析构函数源码:
AutoreleasePool::AutoreleasePool() : _name("") #if defined(COCOS2D_DEBUG) &&(COCOS2D_DEBUG > 0) , _isClearing(false) #endif { _managedObjectArray.reserve(150); PoolManager::getInstance()->push(this); } AutoreleasePool::AutoreleasePool(const std::string &name) : _name(name) #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG> 0) , _isClearing(false) #endif { _managedObjectArray.reserve(150); PoolManager::getInstance()->push(this); } AutoreleasePool::~AutoreleasePool() { CCLOGINFO("deallocing AutoreleasePool: %p", this); clear(); PoolManager::getInstance()->pop(); } 既然发现了为何要这么设计,那么下面来学习PoolManager头文件的声明: class CC_DLL PoolManager { public: CC_DEPRECATED_ATTRIBUTEstatic PoolManager* sharedPoolManager() { return getInstance(); } static PoolManager* getInstance(); CC_DEPRECATED_ATTRIBUTEstatic void purgePoolManager() { destroyInstance(); } static void destroyInstance(); /** *Get current auto release pool, there is at least one auto release pool thatcreated by engine. *You can create your own auto release pool at demand, which will be put intoauto release pool stack. */ AutoreleasePool*getCurrentPool() const; bool isObjectInPools(Ref* obj) const; friend class AutoreleasePool; private: PoolManager(); ~PoolManager(); void push(AutoreleasePool *pool); void pop(); static PoolManager* s_singleInstance; std::vector<AutoreleasePool*> _releasePoolStack; };
用于管理AutoreleasePool对象的PoolManager关键是:std::vector容器,而该类中的push和pop方法其实是封装了C++中push_back和pop_back方法。
3 总结
Cocos2d-x引擎为我们提供了非常方便的内存管理机制,在很多情况下,比如:Scene、Layer、Sprite、Director等,我们只需要使用create方法来创建`自动释放`的对象,还有addChild方法也自动调用了retain,因此我们不需要笨拙的使用new和delete来维护对象的创建和释放。