目标读者:了解 Cocos2d-x 中的节点以及节点树,了解引用计数,了解游戏主循环等概念。


本文首先介绍 Cocos2d-x 3.2 中内存管理的作用,以及各个作用的应用。借由通俗易懂的解释来了解内存管理的过程。其次通过源码解析介绍其内部的实现原理。加深理解,从而在有需要的时候绕开引擎建立自己的内存管理机制。

一、Cocos2d-x 3.2 内存管理的两个方面

  1. 通过加入 autorelease 来自动释放那些创建后未使用的对象。
  2. 通过节点管理来保证对象在弃用后及时地删除。

1、及时释放弃用的对象

使用条件:该对象是Node的子类对象

使用方法:addChild、removeChild

内存管理过程:

addChild //添加对象后,对象可以被使用
removeChild //删除对象后,对象被立刻删除(通过 delete)

2、及时释放未使用的对象

简述新创建的对象如果一帧内不使用,就会被自动释放。(所谓一帧,即是一个gameloop。)

使用条件:对象通过CREAT_FUNC()宏创建或者对象使用autorelease加入了自动释放池。

使用方法:自动实现

内存管理过程:

  • 对象不使用的情况
对象创建 引用+1
对象自动释放 引用-1
  • 对象使用的情况
对象创建 引用+1
对象使用 引用+1 // 通过 addChild 使用对象
对象自动释放 引用-1

引用的初始值为0,如果一帧结束后对象的引用值还是0,那就就会被 delete。



二、内存管理的实现原理

涉及内存管理的文件很多,仅展示直接相关的部分代码。

1、第一部分

Ref 类: 进行引用计数、提供加入自动释放池的接口。

AutoreleasePool 类: 管理一个 vector 数组来存放加入自动释放池的对象。提供对释放池的清空操作。

PoolManager 类: 管理一个 vector 数组来存放自动释放池。默认情况下引擎只创建一个自动释放池,因此这个类是提供给开发者使用的,例如出于性能考虑添加自己的自动释放池。

DisplayLinkDirector 类: 这是一个导演类,提供游戏的主循环,实现每一帧的资源释放。这个类的名字看起来有点怪,但是不用管它。因为这个类继承了 Director 类,也是唯一一个继承了 Director 的类,也就是说完全可以合并为一个类,引擎开发者在源码中有部分说明。

1.1 Ref

// 引用计数变量
unsigned int _referenceCount; // 对象被构造后,引用计数值为 1
Ref::Ref()
: _referenceCount(1) //当Ref对象被创建时,引用计数的值为 1
{
#if CC_ENABLE_SCRIPT_BINDING
static unsigned int uObjectCount = 0;
_luaID = 0;
_ID = ++uObjectCount;
_scriptObject = nullptr;
#endif #if CC_USE_MEM_LEAK_DETECTION
trackRef(this);
#endif
} // 引用+1
void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0");
++_referenceCount;
} // 引用-1 。如果引用为0则释放对象
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0");
--_referenceCount; if (_referenceCount == 0)
{ #if CC_USE_MEM_LEAK_DETECTION
untrackRef(this);
#endif
delete this; // 注意这里 把对象 delete 了
}
} // 提供加入自动释放池的接口。对象调用此函数即可加入自动释放池的管理。
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
} //获取引用计数值
unsigned int Ref::getReferenceCount() const
{
return _referenceCount;
}

1.2 AutoreleasePool

// 存放释放池对象的数组
std::vector<Ref*> _managedObjectArray; // 往释放池添加对象
void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
} // 清空释放池,将其中的所有对象都 delete
void AutoreleasePool::clear()
{
// 释放所有对象
for (const auto &obj : _managedObjectArray)
{
obj->release();
}
// 清空vector数组
_managedObjectArray.clear();
} // 查看某个对象是否在释放池中
bool AutoreleasePool::contains(Ref* object) const
{
for (const auto& obj : _managedObjectArray)
{
if (obj == object)
return true;
}
return false;
}

1.3 PoolManager

// 释放池管理器单例对象
static PoolManager* s_singleInstance; // 释放池数组
std::vector<AutoreleasePool*> _releasePoolStack; // 获取 释放池管理器的单例
PoolManager* PoolManager::getInstance()
{
if (s_singleInstance == nullptr)
{
// 新建一个管理器对象
s_singleInstance = new PoolManager(); // 添加一个自动释放池
new AutoreleasePool("cocos2d autorelease pool");// 内部使用了释放池管理器的push,这里的调用很微妙,读者可以动手看一看
}
return s_singleInstance;
} // 获取当前的释放池
AutoreleasePool* PoolManager::getCurrentPool() const
{
return _releasePoolStack.back();
} // 查看对象是否在某个释放池内
bool PoolManager::isObjectInPools(Ref* obj) const
{
for (const auto& pool : _releasePoolStack)
{
if (pool->contains(obj))
return true;
}
return false;
} // 添加释放池对象
void PoolManager::push(AutoreleasePool *pool)
{
_releasePoolStack.push_back(pool);
} // 释放池对象出栈
void PoolManager::pop()
{
CC_ASSERT(!_releasePoolStack.empty());
_releasePoolStack.pop_back();
}

1.4 DisplayLinkDirector

void DisplayLinkDirector::mainLoop()
{
//第一次当导演
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();//进行清理工作
}
else if (! _invalid)
{
// 绘制场景,游戏主要工作都在这里完成
drawScene(); // 清空资源池
PoolManager::getInstance()->getCurrentPool()->clear();
}
}

根据目前的分析,我们先来捋一捋,待会儿再进一步深入。内存管理的过程是怎么样的呢?首先,创建了一个 Node 对象A,Node 继承Ref,因此 Ref 的引用计数为1;然后,A通过 autorelease 将自己放入自动释放池;drawScene() 完成后,一帧结束,Director 通过释放池将池中的对象 clear(),即对 Node 对象A进行 release() 操作。A的引用计数变为0,执行 delete 释放A对象。

接下来我们继续介绍另外几个与内存管理有关的类。

2、第二部分

Node 类:提供了 addChildremoveChild 方法来创建游戏的节点树。

Vector 类:封装了对于对象的 retain 操作和 release 操作。

2.1 Node

// 添加节点
void Node::addChild(Node *child)
{
CCASSERT( child != nullptr, "Argument must be non-nil");
this->addChild(child, child->_localZOrder, child->_name); // 经过这个方法-->addChildHelper-->insertChild,完成retain操作
} // 移除节点
void Node::removeChild(Node* child, bool cleanup /* = true */)
{
//
if (_children.empty())
{
return;
} //
ssize_t index = _children.getIndex(child);
if( index != CC_INVALID_INDEX )
this->detachChild( child, index, cleanup );//注意这个函数
} // 插入节点
void Node::insertChild(Node* child, int z)
{
_transformUpdated = true;
_reorderChildDirty = true;
_children.pushBack(child);// pushBack方法对节点进行了retain
child->_setLocalZOrder(z);
} // 剥离节点
void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
{ ...// 部分省略 _children.erase(childIndex);// erase方法对节点进行了release
}

2.2 Vector

// 这里仅展示与Node类相关的内存管理的部分

// 将对象入栈,引用+1
void pushBack(T object)
{
CCASSERT(object != nullptr, "The object should not be nullptr");
_data.push_back( object ); object->retain(); // 进行了retain
} // 将目标位置的对象移除
iterator erase(ssize_t index)
{
CCASSERT(!_data.empty() && index >=0 && index < size(), "Invalid index!");
auto it = std::next( begin(), index ); (*it)->release(); // 进行了release return _data.erase(it);
}

到这里,终于可以把故事完整地讲一遍了。内存管理的过程是怎么样的呢?首先,创建了一个 Node 对象A, Node 继承 Ref,因此 Ref 的引用计数为1;然后A又通过 autorelease 将自己放入自动释放池;接着,有个 Node 对象B,B通过 addChild(A) 使得A的引用+1;几个 mainLoop 后,B通过 removeChild(A) 使得A的引用-1;这个 mainLoopdrawScene() 完成后,一帧结束, Director 通过释放池将池中的对象 clear(),即对 Node 对象A进行 release 操作。A的引用计数变为0,执行 delete 释放A对象。

3、高阶用法

之所以称为高阶用法,是因为,如果开发者对 Cocos 的内存管理机制理解不够深刻,那么很可能会用错而导致损失大于收益。另一方面,这类用法在平时很少会用到。

3.1 使用 retain 来延长对象的生存时间

在开发过程中,如果需要使用一个节点对象,但是又不想把它放到节点树里面去。那么就可以使用 retain 来避免对象被自动释放掉。

3.2 使用 PoolManagerpush 来延长对象的生存时间

有些情况下,希望闲置对象晚一帧进行销毁,可以使用 push 把当前释放池推入栈底,那么这一帧结束的时候只会释放刚 push 进去的释放池。

笔者本身还没有机会使用过高阶用法,如果有小伙伴发现了高阶用法在实际问题中的应用,敬请留言交流。

四、参考链接

深入理解 Cocos2d-x 内存管理 (从这篇文章中获得许多启发。)

Cocos2d-x源码(位于Github上,如果连不上尝试使用前缀 https)

引用计数——维基百科(关于引用计数的说明)

Cocos2d-x的内存管理机制概述(里面提到了为什么要有 PoolManager)

cocos2dx 3.2 (24)——内存管理机制(编纂地比较详细)

Cocos2d-x 3.2 的内存管理详解的更多相关文章

  1. Apache Spark 内存管理详解(转载)

    Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色.理解 Spark 内存管理的基本原理,有助于更好地开发 Spark 应用程序和进行性能调优.本文旨在梳理出 ...

  2. 动态内存管理详解:malloc/free/new/delete/brk/mmap

    c++ 内存获取和释放 new/delete,new[]/delete[] c 内存获取和释放 malloc/free, calloc/realloc 上述8个函数/操作符是c/c++语言里常用来做动 ...

  3. spark内存管理详解

    Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色.理解 Spark 内存管理的基本原理,有助于更好地开发 Spark 应用程序和进行性能调优.本文旨在梳理出 ...

  4. MemCache中的内存管理详解

    MC的内存管理机制 1.内存的碎片化 当我们使用C语言或者其他语言进行malloc(申请内存),free(释放内存)等类似的命令操作内存的时候, 在不断的申请和释放的过程中,形成了一些很小的内存片段, ...

  5. 转:C/C++内存管理详解 堆 栈

    http://chenqx.github.io/2014/09/25/Cpp-Memory-Management/ 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了 ...

  6. QF——OC内存管理详解

    堆的内存管理: 我们所说的内存管理,其实就是堆的内存管理.因为栈的内存会自动回收,堆的内存需要我们手动回收. 栈中一般存储的是基本数据类型变量和指向对象的指针(对象的引用),而真实的对象存储在堆中.因 ...

  7. Swift 内存管理详解

    Swift内存管理: Swift 和 OC 用的都是ARC的内存管理机制,它们通过 ARC 可以很好的管理对象的回收,大部分的时候,程序猿无需关心 Swift 对象的回收. 注意: 只有引用类型变量所 ...

  8. Memcached 内存管理详解

    Memcached是一个高效的分布式内存cache,了解memcached的内存管理机制,便于我们理解memcached,让我们可以针对我们数据特点进行调优,让其更好的为我所用. 首先需要我们先了解两 ...

  9. IOS内存管理详解

    一.    基本原理 1.        什么是内存管理 移动设备的内存极其有限,每个app所能占用的内存是有限制的 当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空 ...

随机推荐

  1. UVA-11995

    There is a bag-like data structure, supporting two operations:1 x Throw an element x into the bag.2 ...

  2. require.context批量引入文件

    require.context 是什么 require.context 是由webpack内部实现,require.context在构建时,webpack 在代码中进行解析. 当需要引入文件夹内多个文 ...

  3. httpBasic 认证的URL访问

    httpBasic 认证 要在发送请求的时候添加HTTP Basic Authentication认证信息到请求中,有两种方法: 1.在请求头中添加Authorization: Authorizati ...

  4. 如果你不了解Java的JVM,那真的很难进BAT一线大厂!

    前言 对于开发人员来说,如果不了解Java的JVM,那真的是很难写得一手好代码,很难查得一手好bug.同时,JVM也是面试环节的中重灾区.我们不能为了面试而面试,但是学习会这些核心知识你必定会成为面试 ...

  5. NodeJS4-1静态资源服务器实战_实现访问获取里面的内容

    .gitignore 匹配模式前 / 代表项目根目录 匹配模式最后加 / 代表是目录 匹配模式前加 ! 代表取反 * 代表任意一个字符 ? 匹配任意一个字符 ** 匹配多级目录 统一代码风格配置可以用 ...

  6. C# 中获取一个目录下的目录与文件

    //获得目录下所有文件和子目录使用DirectoryInfo类的GetFileSystemInfos()方法. //获得目录下所有目录 string[] dirs = Directory.GetDir ...

  7. 关于maven依赖关系的问题

    maven可以非常方便的管理jar包依赖问题. 这几天遇到的问题是:使用maven在idea跑flink程序提示 java.lang.ClassNotFoundExceptionjava.lang.N ...

  8. js反爬学习(一)谷歌镜像

    1. url:https://ac.scmor.com/ 2. target:如下链接 3. 过程分析: 3.1 打开chrome调试,进行元素分析.随便定位一个“现在访问” 3.2 链接不是直接挂在 ...

  9. ORA-27140: attach to post/wait facility failed

    Errors in file /home/u01/app/oracle/diag/rdbms/hnybdb21/hnybdb211/trace/hnybdb211_j000_143099.trc:OR ...

  10. C语言基于NIOSII的软件开发及流水灯设计

    一.Quartus II 12.1 (32-Bit)进行硬件设计 1.所需要的系统元器件组成 2.系统电路图 二.Nios II 12.1 Software Build Tools for Eclip ...