Cocos2d-x 3.1 内存管理机制

2023-03-15,,

Cocos2d-x使用的内存管理方式是引用计数。引用计数是一种非常有效的机制。通过给每个对象维护一个引用计数器,记录该对象当前被引用的次数。当对象添加一次引用时,计数器加1;而对象失去一次引用时。计数器减1;当引用计数为0时,标志着该对象的生命周期结束,自己主动触发对象的回收释放。引用计数的重要规则是每个程序片段必须负责任地维护引用计数,在须要维持对象生存的程序段的開始和结束分别添加和降低一次引用计数,这样就能够实现十分灵活的内存管理。

接下来看一下Cocos2d-x 3.1 版本号的源代码是怎么实现引用计数的。

一、Ref

我们都知道差点儿每一个类都继承一个类Ref,打开CCRef.h查看Ref类。去掉其他与引用计数无关的。能够简化为:

// CCRef.h
class CC_DLL Ref
{
public:
void retain(); // 引用计数加1
void release(); // 引用计数减1
Ref* autorelease(); // 将对象交给自己主动释放池 unsigned int getReferenceCount() const; // 获取当前的引用计数 protected:
Ref(); // 构造函数,这里的权限为protected,说明自己不能实例化,子类能够实例化 public:
virtual ~Ref(); protected:
unsigned int _referenceCount; // 引用计数 friend class AutoreleasePool; // 这个先别管
};
// CCRef.cpp
Ref::Ref(): _referenceCount(1) // new一个对象时,引用计数加1
{
} Ref::~Ref()
{
} void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0");
++_referenceCount;
} void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0");
--_referenceCount; if (_referenceCount == 0) // 引用为0时说明没有调用该对象,此时delete对象
{
delete this;
}
} Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this); // 交给自己主动释放池管理
return this;
} unsigned int Ref::getReferenceCount() const
{
return _referenceCount;
}

总结一下,Ref类就做了几件事:

1、Ref自己不能实例化,仅仅能由子类实例化;

2、创建是引用计数为1。

3、调用retain引用计数加1。

4、调用release引用计数减1;

5、调用autorelease并没有使引用计数减1。而是交给自己主动释放池来管理。

那么自己主动释放池是什么呢?肯定跟autorelease方法里面的PoolManager有关。

打开CCAutoreleasePool.h文件查看。发现有两个类,一个是AutoreleasePool,一个是PoolManager,从字面意思看,AutoreleasePool就是自己主动释放池,而PoolManager就是池管理器,这些思路有点清晰了:

1、调用autorelease后对象交给AutoreleasePool来管理。

2、PoolManager是用来管理AutoreleasePool的,说明能够有多个池。

二、AutoreleasePool

接下来一步步看,先看AutoreleasePool自己主动释放池。看简化版本号的:

// AutoreleasePool.h
class CC_DLL AutoreleasePool
{
public:
AutoreleasePool(); // 疑问1:不要在堆上创建,而应该在栈上创建(为什么呢?等下解释)
~AutoreleasePool(); void addObject(Ref *object); // 增加一个Ref对象到释放池
void clear(); // 清理自己主动释放池 private:
std::vector<Ref*> _managedObjectArray; // 用来保存这个自己主动释放池里面增加的全部Ref对象
};
// AutoreleasePool.cpp
AutoreleasePool::AutoreleasePool()
{
_managedObjectArray.reserve(150); // 1、设置容器大小为150
PoolManager::getInstance()->push(this); // 2、新建一个释放池时就增加了释放池管理器中(能够临时放着,等看了PoolManager再回来看)
} AutoreleasePool::~AutoreleasePool()
{
clear(); // 1、清理释放池 PoolManager::getInstance()->pop(); // 2、将释放池从释放池管理器中删除
} void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object); // 增加一个Ref对象到释放池中
} void AutoreleasePool::clear()
{
for (const auto &obj : _managedObjectArray) // 1、调用全部自己主动释放对象的release函数,注意:仅仅有当引用计数为0时才会delete对象,同样对象增加几次就会release几次
{
obj->release();
}
_managedObjectArray.clear(); // 2、清除容器
}

总结一下,AutoreleasePool类就做了几件事:

1、维持一个保存Ref对象的队列,这些Ref对象调用autorelease就会加到该队列,调用addObject函数加入;

2、clear函数对AutoreleasePool管理的全部Ref运行一次release操作。仅仅有当引用计数为0时对象才会delete,增加几次就运行几次release操作。

三、PoolManager

PoolManager是管理释放池的,在AutoreleasePool用到push和pop方法,能够猜到PoolManager应该维持一个存放释放池的栈:

// PoolManager.h
class CC_DLL PoolManager
{
public:
static PoolManager* getInstance(); // PoolManager是个单例模式 static void destroyInstance(); // 删除单例模式创建的对象 AutoreleasePool *getCurrentPool() const; // 获取当前的释放池 private:
PoolManager();
~PoolManager(); void push(AutoreleasePool *pool); // 压入一个释放池
void pop(); // 弹出一个释放池 static PoolManager* s_singleInstance; std::deque<AutoreleasePool*> _releasePoolStack; // 存放自己主动释放池的栈
AutoreleasePool *_curReleasePool; // 当前的自己主动释放池
};
// PoolManager.cpp
PoolManager* PoolManager::s_singleInstance = nullptr; // 获取单例模式时,假设还没创建,则会创建两个释放池并加入到池管理器中
PoolManager* PoolManager::getInstance()
{
if (s_singleInstance == nullptr)
{
s_singleInstance = new PoolManager(); // 第一个池:AutoreleasePool构造函数会将构造的池加入到池管理器中
s_singleInstance->_curReleasePool = new AutoreleasePool();
// 第二个池:将new出来的释放池再一次压入池管理器中
s_singleInstance->_releasePoolStack.push_back(s_singleInstance->_curReleasePool);
}
return s_singleInstance;
} // delete单例模式创建的对象
void PoolManager::destroyInstance()
{
delete s_singleInstance;
s_singleInstance = nullptr;
} PoolManager::PoolManager()
{
} // 析构函数
PoolManager::~PoolManager()
{
while (!_releasePoolStack.empty())
{
AutoreleasePool* pool = _releasePoolStack.back();
_releasePoolStack.pop_back(); // 1、弹出自己主动释放池 delete pool; // 2、delete自己主动释放池对象
}
} // 获取当前释放池
AutoreleasePool* PoolManager::getCurrentPool() const
{
return _curReleasePool;
} void PoolManager::push(AutoreleasePool *pool)
{
_releasePoolStack.push_back(pool); // 1、压入释放池
_curReleasePool = pool; // 2、设为当前释放池
} // 弹出栈顶释放池。并将当前释放池指向新的栈顶释放池
void PoolManager::pop()
{
CC_ASSERT(_releasePoolStack.size() >= 1); _releasePoolStack.pop_back(); if (_releasePoolStack.size() > 1)
{
_curReleasePool = _releasePoolStack.back();
}
}

貌似PoolManager功能更加简单。就是管理释放池。

四、内存管理思路

1、autorelease

new一个对象的时候,调用其autorelease将对象交给自己主动释放池管理

Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurReleasePool()->addObject(this);
return this;
}

2、PoolManager::getInstance()

在获取单例对象时。假设不存在则会创建一个PoolManager对象,这时候会加入两个释放池

引擎自己会维持两个默认的释放池,假设我们没有手动创建释放池。则autorelease对象都加入到栈顶默认释放池。

事实上我还没弄懂这里为什么要有两个默认的释放池,一个也能够的。

3、getCurReleasePool()获取的是当前释放池。addObject()将Ref对象增加当前释放池中。

void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
}

这样。每个调用autorelease的Ref对象都会加入到_managedObjectArray中。

4、自己主动释放池的对象是怎么释放的?看AutoreleasePool:clear()函数

这里循环_managedObjectArray调用里面对象的release。调用1次release,引用计数就减1,当引用计数为0时就delete该对象。

你肯定非常疑惑。在哪里会调用clear函数呢,~AutoreleasePool()会调用,可是那是在delete的时候释放的,我们看到Director类的主循环:

看到这里就明确了吧。每一次主循环都会调用clear来release自己主动释放池的对象。而每一帧会运行一次主循环,也就是每一帧都会清除一次。

五、手动创建释放池

我们已经知道。调用了autorelease()方法的对象将会在自己主动释放池池释放的时候被释放一次。尽管Cocos2d-x已经保证每一帧结束后释放一次释放池,可是假设在一帧之内生成了大量的autorelease对象。将会导致释放池性能下降。因此在生存autorelease对象密集的区域(一般是循环中)的前后,最后手动创建一个释放池。

{
AutoreleasePool pool1; // 手动创建一个释放池
for ()
{
ref->autorelease(); // 循环里面运行autorelease。这些对象会加入到pool1中
}
}

此时。引擎维护三个释放池,我们知道每一帧结束时会运行当前释放池的clear(),所以上面的那些对象就会在第一帧结束时被释放。而那些放在引擎默认释放池的autorelease对象就会在下一帧被释放。错开了释放的时间,这样就不会减少释放池的性能。

看到上面的代码,你会感到疑惑:为什么仅仅有创建释放池。而没有释放。还记得在AutoreleasePool.h中AutoreleasePool构造函数的凝视吗:不要在堆上创建。而应该在栈上。我们知道,new出来对象必须手动delete才干释放,而在栈上的变量。当作用域消失就会释放,如上面的pool1,当运行到最后一个“}”时就会调用其析构函数,看看AutoreleasePool构造和析构函数做了些什么:

创建一个AutoreleasePool是会被push到PoolManager中。而作用域系消失时就会运行析构函数,调用pop从PoolManager中删除该释放池。

这样,在这个局部作用域pool1之间全部的内存管理实际上是交给了AutoreleasePool来完毕。真的是好方法。

假设上面哪里说错了。能够指出来大家讨论讨论。

网上有非常多关于Cocos2d-x内存管理的教程了,大家假设认为我的非常难理解的话能够查考下以下几篇精华:

1、内存管理源代码分析http://cn.cocos2d-x.org/tutorial/show?id=850

2、Cocos2d-x内存管理浅说 http://www.cocoachina.com/newbie/basic/2013/0528/6290.html

3、Cocos2d-x内存管理的一种实现 http://www.cocoachina.com/applenews/devnews/2013/0531/6315.html

4、深入理解Cocos2d-x内存管理 http://www.cocoachina.com/applenews/devnews/2013/0621/6455.html

Cocos2d-x 3.1 内存管理机制的相关教程结束。

《Cocos2d-x 3.1 内存管理机制.doc》

下载本文的Word格式文档,以方便收藏与打印。