cocos2d-x 内存管理
环境: cocos 3.10 Xcode
C++ 内存管理简介
分配方式:
1. 静态分配,内存在编译的时候就已经分配好,该内存在程序运行期间都存在。比如:全局变量,static静态变量等。
2. 栈分配,在函数内部的局部变量上,函数执行结束后自动释放。效率高,但分配容量有限。
3. 堆分配,又称动态内存分配,使用new创建,delete销毁。使用灵活,但问题很多。
常见问题:
1. 创建失败,没有添加if判定是否不为NULL,就直接使用
2. 创建成功后,没有初始化
3. 销毁后,没有置为NULL,导致了野指针;或者根本就没释放,导致内存泄露
4. 使用越界,在数组中尤为突出
5. 重复释放
cocos2d-x 内存管理
1. Ref
cocos2d-x中的所有对象几乎都继承于Ref基类。Ref的基类的主要就是对对象进行引用计数管理。
class CC_DLL Ref: { public: void retain(); // 增加引用计数 void release(); // 减少引用计数,引用计数为0时进行释放 Ref* autorelease(); // 添加自动缓存池 unsigned int getReferenceCount(); // 获取对象引用计数 protected: Ref(); // 构造函数设为protected,保证可被继承且智能子类实例化 protected: unsigned int _referenceCount; // 引用计数数目 friend class AutoreleasePool; // 自动释放池 }
retain()和release() 没有作任何特别事情,仅仅是负责对象的引用计数增加/减少而已。
Ref::Ref(): _referenceCount(1) // new一个对象时,引用计数加1 { } void Ref::retain() { CCASSERT(_referenceCount > 0, "reference count should be greater than 0"); ++_referenceCount; } void Ref::release() { CCASSERT(_referenceCount > 0, "reference count should be greater than 0"); --_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 #if CC_REF_LEAK_DETECTION untrackRef(this); #endif delete this; } } Ref* Ref::autorelease() { // 将对象添加到自动释放池管理器中 PoolManager::getInstance()->getCurrentPool()->addObject(this); return this; }
假设我们使用new创建一个对象,简单的示例:
auto node = new Node(); // 引用计数为1 addChild(node); // 引用计数为2 node->removeFromParent(); // 引用计数为1 node->release(); // 引用计数为0,释放对象
如果忘记使用release, 就会导致内存泄漏。为此,cocos添加了autorelease帮助我们自动管理内存。
// 通过autorelease()方法将对象添加到AutoreleasePool自动释放池中 Ref* Ref::autorelease() { PoolManager::getInstance()->getCurrentPool()->addObject(this); return this; }
cocos2d-x在每帧结束时都会清理AutoreleasePool自动释放池中的对象。主要通过mainLoop进行刷新,代码如下:
// widnows中使用DisplayLinkDirector, 继承于Director,用于同步Timer,刷新屏幕 void Director::mainLoop() { if (! _invalid) { drawScene(); // 清理引用计数为1的 PoolManager::getInstance()->getCurrentPool()->clear(); } }
在每帧结束后,都会调用AutoreleasePool下的clear进行引用计数减1的操作,代码如下:
void AutoreleasePool::clear() { #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) _isClearing = true; #endif std::vector<Ref*> releasings; releasings.swap(_managedObjectArray); for (const auto &obj : releasings) { obj->release(); // 引用计数 -1 } #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) _isClearing = false; #endif }
这样设计的话,若每帧结束后,对象创建且未被使用,引用计数将从1变为0,对象将被销毁,比如:
auto node = new Node(); // 引用计数1 node->autorelease(); // 加入自动释放池
若在第一帧结束前,被addChild到屏幕中,引用计数从1变为了2,对象将不被销毁。比如:
auto node = new Node(); // 引用计数1 node->autorelease(); // 加入自动释放池 addChild(node); // 引用计数2
cocos为了简化上述的使用,提供了静态方法:create(),以Node::create()为例:
Node * Node::create() { Node * ret = new (std::nothrow) Node(); // 检测对象是否创建成功并初始化 if (ret && ret->init()) { ret->autorelease(); // 添加到自动释放池中 } else { CC_SAFE_DELETE(ret); } return ret; }
该create又被称为“二次构建”,意为创建对象时,构造函数仅用于分配内存,而初始化借助于init来完成。
这样做的原因是cocos2d-x是从cocos2d-iphone移植而来,一方面是兼容,一方面是防止程序忘记初始化的操作等。
2.AutorelasePool 和 PoolManager
关于autorelease的实现主要通过AutoreleasePool释放池,其代码为:
AutoreleasePool::AutoreleasePool() : _name("") { // 设置容器大小 _managedObjectArray.reserve(150); PoolManager::getInstance()->push(this); } AutoreleasePool::AutoreleasePool(const std::string &name) : _name(name) { _managedObjectArray.reserve(150); PoolManager::getInstance()->push(this); } AutoreleasePool::~AutoreleasePool() { // 清理释放池 clear(); // 将释放池从管理器中移除 PoolManager::getInstance()->pop(); } void AutoreleasePool::addObject(Ref* object) { // 添加对象到释放池中 _managedObjectArray.push_back(object); } void AutoreleasePool::clear() { // 循环遍历对象的release, 当引用技术为0的,进行删除 std::vector<Ref*> releasings; releasings.swap(_managedObjectArray); for (const auto &obj : releasings) { obj->release(); // } }
AutoreleasePool可有多个,为了管理,cocos创建了PoolManager管理类。其主要代码如下:
// PoolManager.h class CC_DLL PoolManager { public: static PoolManager* getInstance(); // 获取当前的自动释放池 AutoreleasePool *getCurrentPool() const; // 判定对象池管理器中是否存在该对象 bool isObjectInPools(Ref* obj) const; private: // 压入和弹出释放池中 void push(AutoreleasePool *pool); void pop(); };
PoolManager主要的功能就是管理释放池相关.
...