1. 写在前面
C++在C11之前,都是使用new分配内存,delete释放内存。这看起来是不是非常轻松?其实这会暴露出许多缺点:
- 内存重复释放
- 野指针:指向的内存已经被释放了,但是指针还在使用
- 内存泄露:不再使用的内存没有释放,内存占用率过高
C11就引出了智能指针来解决以上的三个问题。
我已经有博客提到智能指针了,想多了解的朋友可以去看看:C++多线程下的shared_ptr、C11新特性之智能指针。
shared_ptr:多个智能指针可共享同一内存,使用引用计数
unique_ptr:不能和其他智能指针指向同一个内存,直接赋值给其他指针会报错,但是可以使用move来转移,转移后之前的智能指针就失效。
week_ptr:使用lock()函数,它可以检测weak_ptr访问的对象是否存在
既然智能指针看起来解放了大家去释放内存的步骤,那么为什么在cocos里面我们不使用智能指针来管理内存呢?
- 性能损失
- 仍要显示声明指针
2. Ref
cocos2dx虽然没有使用智能指针,但是它用了一个与智能指针非常相似的东西,也就是下面我们要说的Ref。
Ref的主要流程:
当对象被创建时候,引用计数为1。为了保证对象的存在,可以调用retain函数保持对象,retain会使其引用计数加1,如果不需要这个对象可以调用release函数,release使其引用计数减1。当对象的引用计数为0的时候,引擎就知道不再需要这个对象了,就会释放对象内存。
class Ref:
def retain():pass 增加引用
def release():pass 减少引用
def autorelease():pass 交给自动释放池
def getRefreenceCount():pass
self._referenceCount = 0
#friend class AutoreleasePool
在使用Node节点对象时候,addChild函数可以保持Node节点对象,使引用计数加1。通过removeChild函数移除Node节点对象,使引用计数减1。
2.1 Ref::retain()
retain()对其引用计数加1
void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
++_referenceCount;
}
2.2 Ref::release()
release()先对引用计数-1,然后判断引用计数是否为0,若为0则删除对象
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
对其引用计数值进行-1
--_referenceCount;
引用计数为0,删除对象
if (_referenceCount == 0)
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
auto poolManager = PoolManager::getInstance();
if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
{
CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
}
#endif
从保存Ref*的list中删除
#if CC_REF_LEAK_DETECTION
untrackRef(this);
#endif
删除该对象
delete this;
}
}
2.3 Ref::autorelease()
autorelease()将节点添加到自动释放池中,它由PoolManager进行管理。
调用内存管理PoolManager的单例对象中存储的自动释放池对象的addObject函数加入当前的Ref实例对象的指针
cocos2dx为了不用显式调用AutoRelease,一般让程序员通过create来获取对象,而create里帮我们调用好了AutoRelease。autorelease主要应用在ui元素上。
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
3. CCAutoreleasePool
这个类主要含有下面几个方法
void addObject(Ref *object); 将Ref添加到自动释放池
void clear(); 清除自动释放池所有对象
bool contains(Ref* object) const; 检测自动释放池中是否有Object对象
void dump(); 打印删除的对象的地址
3.1 构造函数
构造函数里可以看出,每一个autoreleasePool对象都有一个管理obj的队列managedObjectArray用于存放obj,并且所有Pool对象都是放在poolManage的单例对象的堆中。
AutoreleasePool::AutoreleasePool()
: _name("")
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{ 容器扩容,申请保存ref的vector的size增加150
_managedObjectArray.reserve(150);
把AutoreleasePool添加到管理类生明的vector中
PoolManager::getInstance()->push(this);
}
3.2 析构函数
析构函数是先清除自动释放池所有obj对象,然后再将自己从poolManage的堆中弹出
AutoreleasePool::~AutoreleasePool()
{
CCLOGINFO("deallocing AutoreleasePool: %p", this);
clear();
PoolManager::getInstance()->pop();
}
3.3 addObject
把object添加到managedObjectArray自动释放池
void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
}
3.4 clear
clear就是将managedObjectArray里的所有obj都执行release
void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = true;
#endif
std::vector<Ref*> releasings;
releasings.swap(_managedObjectArray);
//遍历自动释放池managedObjectArray里存放的所有的Ref
for (const auto &obj : releasings)
{
//调用obj的release(),对obj的引用计数-1(如果对象引用计数为0则删除)
obj->release();
}
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = false;
#endif
}
4. PoolManager
- PoolManager类对象是一个单例对象singleInstance ,整个工程中只有一个
- PoolManager类维护着一个堆releasePoolStack,用来存放工程中所有的AutoreleasePool对象;
- 名字叫"cocos2d autorelease pool"的自动释放池对象保存工程中大部分渲染的节点以及我们通过create()创建的对象,这个AutoreleasePool对象是工程刚启动时就生成的;
4.1 顺便通过PoolManager来重新学一下
static PoolManager* s_singleInstance; //poolmanager的实例,再强调一遍这是个单例对象
PoolManager* PoolManager::s_singleInstance = nullptr;
PoolManager* PoolManager::getInstance()
{
//判断是否存在singleInstance
if (s_singleInstance == nullptr)
{
//如果singleInstance 为nullptr,new一个PoolManager单例
s_singleInstance = new (std::nothrow) PoolManager();
//new一个AutoreleasePool并取名字为"cocos2d autorelease pool"
new AutoreleasePool("cocos2d autorelease pool");
}
return s_singleInstance;
}
void PoolManager::destroyInstance()
{
delete s_singleInstance;
s_singleInstance = nullptr;
}