1 动机
虽然目前计算机内存已经非常便宜,但也不是说我们可以毫无节制的不恰当的使用。尤其是移动端的内存很吃紧,整个手机的APP共享那么点内存,说不定手机卡死或者运行的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来维护对象的创建和释放。