在cocos2dx框架中,我们似乎有一种感觉–不用手动释放内存,其实,这些都是框架帮我们实现好的
cocos2dx中采用了一种比较古老的方式—–引用计数的方式来实现内存的自动回收,如果是了解c++11中的std::shared_ptr的同学肯定不会觉得陌生,原理是类似的。
我们首先来明确一个问题,cocos2dx是在什么地方释放内存的呢?
我们来看源码:
CCDirector.cpp
//简化版
void DisplayLinkDirector::mainloop()
{
...
drawScene();
PoolManager::getInstance()->getCurrentPool()->clear();
...
}
这个是cocos2dx的主循环,我们不难看出,在每一帧绘制结束后,PoolManager
就会进行一个clear()
操作,这里,就是我们cocos2dx内存管理的入口了
那么这个PoolManager
又是怎么实现的呢?
这个就要从我们Ref
类讲起了
class CC_DLL Ref
{
public:
void retain();
void release();
Ref* autorelease();
unsigned int getReferenceCount() const;
...
protected:
unsigned int _referenceCount;
friend class AutoreleasePool;
}
根据我们上面主循环的代码PoolManager
应该是实现内存自动释放的关键
class CC_DLL PoolManager
{
public:
...
static PoolManager* getInstance();
AutoreleasePool *getCurrentPool() const;
friend class AutoreleasePool;
...
private:
std::vector<AutoreleasePool*> _releasePoolStack;
void push(AutoreleasePool *pool);
void pop();
}
...
AutoreleasePool* PoolManager::getCurrentPool() const
{
return _releasePoolStack.back();
}
这是一个单例类,其中调用的getCurrentPool
方法返回的只是一个AutoreleasePool
的向量
并且这个AutoreleasePool
是他的友缘类,而且也是Ref
的友缘类
我们来看看AutoreleasePool
的实现
class CC_DLL AutoreleasePool
{
public:
void addObject(Ref *object);
void clear();
bool contains(Ref* object) const;
void dump();
private:
std::vector<Ref*> _managedObjectArray;
std::string _name;
}
void AutoreleasePool::clear()
{
...
std::vector<Ref*> releasings;
releasings.swap(_managedObjectArray);
for (const auto &obj : releasings)
{
obj->release();
}
...
}
Ref::relase的实现
void Ref::release()
{
...
--_referenceCount;
if (_referenceCount == 0)
{
delete this;
}
...
}
大体上就是这样一个流程
那么如何保留我们的实例不被释放呢?
很简单,使用我们的retain
方法,其实就是将引用计数加一
void Ref::retain()
{
...
++_referenceCount;
...
}
因为每次进行clear()
的时候,会把_managedObjectArray
清空
也就是说,每个继承自Ref
的类实例化后只会进行一次release
,所以我们调用retain
将引用计数加一后就不用担心被释放的问题了
那么reatin
后的实例如何释放呢?
也很简单, 调用 autorelease
或者 release
即可
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
}
Autorelease
会将当前实例加入到待释放数组中,所以就可以实现自动释放了
还有一个问题,既然是引用计数,那么最开始的引用数是在哪里赋值的呢?
是在Ref
的构造函数中
Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
...
{
...
}
所以,我们在cocos2dx开发中,一般使用框架中的Vector
因为Vector
在pushBack()
的时候,会调用一次retain
官方文档上的一些话:
CCAutoreleasePool不能被开发者自己创建。Cocos2d-x会为我们每一个游戏创建一个自动释放池实例对象,游戏开发者不能新建自动释放池,仅仅需要专注于release/retain cocos2d::CCObject的对象。
CCAutoreleasePool不能被用在多线程中,所以假如你游戏需要网络线程,请仅仅在网络线程中接收数据,改变状态标志,不要这个线程里面调用cocos2d接口。下面就是原因:
CCAutoreleasePool的逻辑是,当你调用object->autorelease(),object就被放到自动释放池中。自动释放池能够帮助你保持这个object的生命周期,直到当前消息循环的结束。在这个消息循环的最后,假如这个object没有被其他类或容器retain过,那么它将自动释放掉。例如,layer->addChild(sprite),这个sprite增加到这个layer的子节点列表中,他的声明周期就会持续到这个layer释放的时候,而不会在当前消息循环的最后被释放掉。
这就是为什么你不能在网络线层中管理CCObject生命周期,因为在每一个UI线程的最后 ,自动释放对象将会被删除,所以当你调用这些被删掉的对象的时候,你就会遇到crash。
简而言之,这只有两种情况你需要调用release()方法
你new一个cocos2d::CCObject子类的对象,例如CCSprite,CCLayer等。
你得到cocos2d::CCObject子类对象的指针,然后在你的代码中调用过retain方法。