目标读者:了解 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. 线阵CCD-TCD1209采集系统&驱动设计

    关键字:CPLD+AD9945+TCD1209+CY7C68013A TCD1209,一款经典的CCD线阵单色传感器.本次设计一款基于usb2.0高速采集图像. CPLD+AD9945+TCD1209 ...

  2. Altium PCB二维码Logo设计(转 crazybingo)

    Altium PCB二维码Logo设计 每次设计PCB的时候,都会在空白部分放Logo上去,一来板卡显得更充实,二来更有成就感一些... 今天突然想着...这两年二维码越来越火,火到快爆发,不如在板卡 ...

  3. 使用java语言实现八皇后问题

    八皇后问题,在一个8X8的棋盘中,放置八个棋子,每个棋子的上下左右,左上左下,右上右下方向上不得有其他棋子.正确答案为92中,接下来用java语言实现. 解: package eightQuen; / ...

  4. nmap扫描进阶、msfconsole攻击入门(网安全实训第二天)

    本期内容:nmap扫描.msfconsole攻击入门 1. nmap扫描进阶 2.msfconsole攻击入门 1.nmap扫描进阶 (1)nmap命令 nmap --sP -iL abin.txt ...

  5. 【React】393 深入了解React 渲染原理及性能优化

    如今的前端,框架横行,出去面试问到框架是常有的事. 我比较常用React, 这里就写了一篇 React 基础原理的内容, 面试基本上也就问这些, 分享给大家. React 是什么 React是一个专注 ...

  6. 【ZooKeeper系列】2.用Java实现ZooKeeper API的调用

    温馨提示:在这里我再次提个小要求,希望大家能习惯看官方文档,文档虽然是英文但用词都比较简单,基本都能看懂文档表达的意思.授之以鱼不如授之以渔的道理相信大家都明白,也希望通过猿人谷的这个ZooKeepe ...

  7. 通过url返回的状态来抢注好的用户名

    之前在注册很多网站时都想取一个好的用户名,但是不知道那些被注册了没有,通常时一个一个测试,但是很慢当时就想过这个思路,由于懒并没有去搞 主要思路就是:很多网站的用户主页的Url都存在用户名,替换为自己 ...

  8. NodeJS4-5静态资源服务器实战_优化压缩文件

    浏览器控制台看一下RequestHeader有一个Accept-Encoding,而RespondHeaders中也会有一个Content-Encoding和他进行对应. Accept-Encodin ...

  9. 牛客国庆集训派对Day1 L New Game!(堆优化dijkstra+建图)

    链接:https://ac.nowcoder.com/acm/contest/201/L来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 1048576K,其他语言2097 ...

  10. Centos7上离线搭建PHP-7.2.26

    一.下载php源安装包,解压至/home/php目录下 下载地址:https://www.php.net/distributions/php-7.2.26.tar.gz [root@localhost ...