作者:tangyikejun
出处:http://www.cnblogs.com/tangyikejun/

本文采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

本文首先介绍 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。


^^ 版权衔接线 vv     全文出处:http://www.cnblogs.com/tangyikejun/p/4361638.html

二、内存管理的实现原理

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

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_BINDINGstatic unsigned int uObjectCount = 0;_luaID = 0;_ID = ++uObjectCount;_scriptObject = nullptr;
#endif#if CC_USE_MEM_LEAK_DETECTIONtrackRef(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_DETECTIONuntrackRef(this);
#endifdelete 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 类:提供了 addChild 和 removeChild 方法来创建游戏的节点树。
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方法对节点进行了retainchild->_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(); // 进行了releasereturn _data.erase(it);
}

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

3、高阶用法

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

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

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

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

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

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

四、参考链接

深入理解 Cocos2d-x 内存管理 (从这篇文章中获得许多启发。)
Cocos2d-x源码(位于Github上,如果连不上尝试使用前缀 https)
引用计数——维基百科(关于引用计数的说明)
Cocos2d-x的内存管理机制概述(里面提到了为什么要有 PoolManager)
cocos2dx 3.2 (24)——内存管理机制(编纂地比较详细)

-----------------------------------------------------------------------------------
再补充一篇大佬的博客:http://www.cnblogs.com/mmidd/p/3746256.html

Cocos2d-x 3.2 的内存管理详解相关推荐

  1. Spark 内存管理详解(下):内存管理

    本文转自:Spark内存管理详解(下)--内存管理 本文最初由IBM developerWorks中国网站发表,其链接为Apache Spark内存管理详解 在这里,正文内容分为上下两篇来阐述,这是下 ...

  2. FreeRTOS笔记(六):五种内存管理详解

    不同的嵌入式系统对于内存分配和时间要求不同.FreeRTSO将内存分配作为移植层的一部分,这样FreeRTOS使用者就可以设用自己的合适的内存分配方法. 当内核需要分配内存时可以调用pvPortMal ...

  3. Nginx内存管理详解

    目录: 1.Nginx内存管理介绍 2.Nginx内存池的逻辑结构 3.Nginx内存池的基本数据结构 4.内存池基本操作介绍 5.内存池管理源码详解 6.内存池使用源码详解 7.小结 1.Nginx ...

  4. Apache Spark 内存管理详解

    原文出处: IBM developerWorks Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色.理解 Spark 内存管理的基本原理,有助于更好地开发 ...

  5. Spark2.1 内存管理详解

    本文中阐述的原理基于 Spark 2.1 版本,阅读本文需要读者有一定的 Spark 和 Java 基础,了解 RDD.Shuffle.JVM 等相关概念. 在执行 Spark 的应用程序时,Spar ...

  6. C/C++内存管理详解以及内存泄露的检测

    文章目录 前言 一.C/C++内存分布? 1.内存布局示意图: 2.内存存放的数据类型 二.C/C++内存管理 1.C内存管理 2.C++内存管理 三.内存泄漏 1.概念 2.Windows平台下检测 ...

  7. Swift 中的内存管理详解

    这篇文章是在阅读<The Swift Programming Language>Automatic Reference Counting(ARC,自动引用计数)一章时做的一些笔记,同时参考 ...

  8. 内存管理模拟程序c语言,C语言 内存管理详解

    本文出自: 伟大的Bill Gates曾经失言: 640K ought to be enough for everybody -Bill Gates 1981. 程序员们经常编写内存管理程序,往往提心 ...

  9. C/C++内存管理详解

    内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C++中无处不在,内存泄 ...

  10. spark从入门到精通spark内存管理详解- 堆内堆外内存管理

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

最新文章

  1. SqlServer分页排序存储过程 V1.0
  2. 注重网站用户体验优化就要避开前方弯道
  3. 跨平台C++开源码的两种经常使用编译方式
  4. boost使用学习总结
  5. 计算机 数据库知识点,数据库知识点总结
  6. 【Demo 0011】多媒体播放器
  7. 计算机应用基础第二章,计算机应用基础第二章上机操作题
  8. flume 一对多hdfs_10PB 规模的 HDFS 数据在 eBay 的迁移实战
  9. 辅助出售网站源码_出售网站意味着出售社区
  10. python3.7.3配置环境变量_配置环境变量切换到python3.7
  11. Android 常用语句
  12. mysql 镜像安装方法_MySql镜像安装
  13. BZOJ3687 简单题
  14. SQLPrompt10带注册机(支持SSMS v18.5)
  15. 文件mime类型大全
  16. 物理学家揭示深度学习原理:神经网络与宇宙本质惊人关联
  17. 人脸识别接口_智慧小区人脸识别门禁系统室外人脸识别门禁终端厂家
  18. ATX电源故障导致某些主板不能启动
  19. Excel根据某列的值,用不同颜色区分数据行
  20. 如何理解yield的用法

热门文章

  1. Objective C类方法load和initialize的区别
  2. [Share]浏览器的历史
  3. phpcms V9.6.0版本整合百度ueditor1.4.3.2,包括水图片上传水印
  4. 不让editText自动获取焦点
  5. Spring Mvc前台访问报404
  6. Vitamio不支持特性列表(本文会持续更新 2013-03-13)
  7. popen函数的实现
  8. XML文件源码察看器(三)
  9. 女生转行成为程序员的顾虑和优势
  10. 医疗物联网解决方案提供商“识凌科技”完成C轮融资