Cocos内存管理解析 CCRef/retain/release/autorelease
Cocos内存管理源码(autorelease解析)
背景
这段时间在做项目的时候,需求需要往spine动作的挂点上绑定按钮节点,由于按钮在编辑器中是加在已有节点上的,所以在往spine上添加挂点时,需要先移除按钮,然后再绑定的挂点上。
local spineAnim = sp.SkeltonAnimation:create(skeletonFile, atlasFile, 1.0, true)
local btnGame = roleNode:getChildByName("btnGame")
btnGame:removeFromParent()
spineAnim:addSlotBindInfo("qianwangduiju", btnGame, Defind.slotBindType.slotBindType_all)
如果直接这样写,会在某种情况导致btnGame按钮节点丢失,无法正常挂载再spine动画节点上,后续优化了此方案
local spineAnim = sp.SkeltonAnimation:create(skeletonFile, atlasFile, 1.0, true)
local btnGame = roleNode:getChildByName("btnGame")
btnGame:retain()
btnGame:removeFromParent()
btnGame:autorelease()
spineAnim:addSlotBindInfo("qianwangduiju", btnGame, Defind.slotBindType.slotBindType_all)
在移除按钮之前,先retain一下,这样引用计数加1,就不会导致内存被回收,再调用autorelease,此时并不会release对象,这时会将此节点加入_managedObjectArry对象池中,在Director的mainLoop中会调用PoolManager::getInstance()->getCurrentPool()->clear();
具体详情解析,请接着看
在cocos2dx-3.8中的自动内存管理是用引用计数来实现的,对于老版本的coocs引用计数使用的是CCObejct,但这个类后续被弃用了,使用CCRef代替,cocos中几乎所有的类都是继承于CCRef
CCRef基本原理就是其内部存在一个引用计数_referenceCount,当这个计数为0时,就会被释放。引用计数通过retain,release操作。
Ref从创建到销毁的过程
举个栗子,向屏幕中添加一个Button来测试Ref的创建和销毁,首先创建一个Button
auto button = Button::create();
button->setName("myButton");
addChild(button);
以上代码就是向屏幕中添加一个button,让我们看看create做了什么
Button* Button::create()
{
Button* widget = new (std::nothrow) Button();
if (widget && widget->init())
{
widget->autorelease();
return widget;
}
CC_SAFE_DELETE(widget);
return nullptr;
}
create函数是一个工厂方法,cocos中很多类都实现了这个方法,其中可以看到ret->autorelease();,这个函数就是把当前对象加入到自动释放池内,对于自动释放池下面会详细讲解。(注:Ref初始化的时候引用计数为1不是0)
接下来看下addChild()接口,此处截取了一部分
void Node::addChild(Node *child){
CCASSERT( child != nullptr, "Argument must be non-nil");
this->addChild(child, child->_localZOrder, child->_name);
}
void Node::addChild(Node* child, int localZOrder, const std::string &name){
...
addChildHelper(child, localZOrder, INVALID_TAG, name, false);
}
void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag){
...
this->insertChild(child, localZOrder);
...
}
void Node::insertChild(Node* child, int z){
_transformUpdated = true;
_reorderChildDirty = true;
_children.pushBack(child);
child->_localZOrder = z;
}
最终跳转到insertChild中,通过 _children.pushBack把 button加入到_children中去,到底引用计数在哪里+1操作?答案在pushBack操作中,_children是cocos为Ref量身定制的向量Vector<T>,这个向量只能给继承Ref的类使用
void pushBack(T object){
...
_data.push_back( object );
object->retain();
}
代码中可以看到object->retain(),对添加进来的对象引用+1操作,那么什么时候-1呢?
当我们移除场景的时候,应该释放场景中的button的。Node被移除时会调用当前的Node的父亲的removeChild函数,此函数最后会调用Node的cleanup函数,cleanup函数时递归函数,会遍历所有子节点。当cleanup完之后会从父节点的_children这个向量中删除,此时就会调用release函数
//当某个儿子节点cleanup完之后会调用_children.earse(childIndex)
iterator erase(ssize_t index){
...
auto it = std::next( begin(), index );
(*it)->release();
return _data.erase(it);
}
release函数就是当前实例的引用计数-1,如果-1后为0那么释放内存
void Ref::release(){
...
--_referenceCount;
if (_referenceCount == 0){
...
delete this;
}
}
自动释放池
Ref中的autorelease函数,咋一看感觉内存不需要我来管了,他会自动释放。然而这个自动和我脑子里面的自动向差一个孙猴子的跟头,毕竟c++不是java,先看看autorelease的源码
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
代码很短,autorelease并没有release,而是把对象加入到了对象池中。那么这个对象池是什么时候去release里面的对象呢?接下来就要看Director的mainLoop函数了,这个函数在Director中实现。
void Director::mainLoop()
{
if(_purgeDirectorInNextLoop)
{
...
}
else if(_restartDirectorInNextLoop)
{
...
}
else if(!_invalid)
{
drawScene();
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
mainLoop是每一帧调用的函数,我们发现cocos在每一帧结束绘制drawScene之后都会调用PoolManager::getInstance()->getCurrentPool()->clear();的操作,接下来我们看看clear的实现细节。
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();
}
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = false;
#endif
}
我们发现,在clear里面对所有在_managedObjectArray中的所有对象都进行一次release操作,并把它从_managedObjectArray中删掉。_managedObjectArray是什么,查看前一段代码中addObject的实现细节就知道,autorelease就是把当前对象加入到_managedObjectArray中
也就是说,我们创建的Button的时候引用计数为1,然后调用autorelease添加到_managedObjectArray中,之后又被addChild到屏幕中,此时引用计数为2。当一帧绘制结束的时候会系统会调用释放池的clear函数,此函数会遍历所有在自动释放池内的对象并release,最后从对象池中删除之(所以第二帧结束后不会被再次调用release了),此时引用计数为1。当我们把当前场景移除的时候会调用release把引用计数减少至0,并从内存中释放。
Cocos内存管理解析 CCRef/retain/release/autorelease的更多相关文章
- objective-C 的内存管理之-自动释放池(autorelease pool)
如果一个对象的生命周期显而易见,很容易就知道什么时候该new一个对象,什么时候不再需要使用,这种情况下,直接用手动的retain和release来判定其生死足矣.但是有些时候,想知道某个对象在什么时候 ...
- iOS 内存管理-copy、 retain、 assign 、readonly 、 readwrite、nonatomic、@property、@synthesize、@dynamic、IB_DESIGNABLE 、 IBInspectable、IBOutletCollection
浅谈iOS内存管理机制 alloc,retain,copy,release,autorelease 1)使用@property配合@synthesize可以让编译器自动实现getter/setter方 ...
- C#内存管理解析
前言:对于很多的C#程序员来说,经常会很少去关注其内存的释放,他们认为C#带有强大的垃圾回收机制,所有不愿意去考虑这方面的事情,其实不尽然,很多时候我们都需要考虑C#内存的管理问题,否则会很容易造成内 ...
- Linux内存管理解析(一) : 分段与分页机制
背景 : 在此文章里会从分页分段机制去解析Linux内存管理系统如何工作的,由于Linux内存管理过于复杂而本人能力有限.会尽量将自己总结归纳的部分写清晰. 从实模式到保护模式的寻址方式的不同 : 1 ...
- Linux内存管理解析(二) : 关于Linux内存管理的大体框架
什么是内存管理 ? 首先内存管理管理的主要对象是虚拟内存,但是虚拟内存对应的映射主要为物理内存,其次也可能通过交换空间把虚拟内存与硬盘映射起来,既然如此,那我们先了解物理内存的管理. 对于物理内存而言 ...
- 深入解析alloc/retain/release/dealloc实现
首先通过GNUstep上得源码来叙述各个函数的实现(GNUstep是Cocoa框架的互换框架,二者的行为和实现方式很相似) GNUstep源码中NSObject类的alloc方法: id = obj ...
- linux内存管理解析1----linux物理,线性内存布局及页表的初始化
主要议题: 1分页,分段模式及实模式 2Linux分页 3linux内存线性地址空间布局及物理内存空间布局 4linux页表初始化及代码解析 1.1.1内存寻址和保护模式 在X86平台上,内存控制单元 ...
- Linux内存管理解析(三) : 内核对内核空间的内存管理
内核采用 struct page 来表示一个物理页,在其中记载了诸多物理页的属性,比如 物理页被几个线程使用(如若没有则表示该页可以释放),页对应的虚拟地址. 首先需要知道的是,分配物理页可以分为两个 ...
- WINDOWS页式内存管理解析
jpg 改 rar
- iOS 进阶—— iOS 内存管理
1 似乎每个人在学习 iOS 过程中都考虑过的问题 alloc retain release delloc 做了什么? autoreleasepool 是怎样实现的? __unsafe_unretai ...
随机推荐
- C++ ASIO 实现异步套接字管理
Boost ASIO(Asynchronous I/O)是一个用于异步I/O操作的C++库,该框架提供了一种方便的方式来处理网络通信.多线程编程和异步操作.特别适用于网络应用程序的开发,从基本的网络通 ...
- Java单元测试及常用语句
1 前言 编写Java单元测试用例,即把一段复杂的代码拆解成一系列简单的单元测试用例,并且无需启动服务,在短时间内测试代码中的处理逻辑.写好Java单元测试用例,其实就是把"复杂问题简单化, ...
- HTML一键打包APK工具最新版1.9.2更新(附下载地址)
HMTL网址打包APK,可以把本地HTML项目, Egret游戏,网页游戏,或者网站打包为一个安卓应用APK文件,无需编写任何代码,也无需配置安卓开发环境,支持在最新的安卓设备上安装运行. 打包软件会 ...
- 2.14 PE结构:地址之间的转换
在可执行文件PE文件结构中,通常我们需要用到地址转换相关知识,PE文件针对地址的规范有三种,其中就包括了VA,RVA,FOA三种,这三种该地址之间的灵活转换也是非常有用的,本节将介绍这些地址范围如何通 ...
- SpringBoot 后端配置 Https 教程
以阿里云为例子 1. 申请 SSL 证书 1. 注册域名 打开阿里云官网,搜索域名 点击域名注册,输入域名,点击搜索 选择心仪的域名,点击购买,打钱 进入域名控制台,进行实名认证 2. 申请 SSL ...
- 超级实用!React-Router v6实现页面级按钮权限
大家好,我是王天- 今天咱们用 reac+reactRouter来实现页面级的按钮权限功能.这篇文章分三部分,实现思路.代码实现.踩坑记录. 嫌啰嗦的朋友,直接拖到第二章节看代码哦. 前言 通常情况下 ...
- 深入理解Python虚拟机:super超级魔法的背后原理
深入理解Python虚拟机:super超级魔法的背后原理 在本篇文章中,我们将深入探讨Python中的super类的使用和内部工作原理.super类作为Python虚拟机中强大的功能之一,super ...
- CF1352D
题目简化和分析: 这题可以直接按照题意进行模拟,当然有些细节需要注意. 翻译的不足:这里的回合指任意一个人吃掉都算,而不是双方一个回合,最后一个人即使不满足也算一个回合. 我们可以采用两个指针模拟两个 ...
- NOI Linux 食用指北
写这篇 blog 的原因是某个小朋友要考 CSP 了还不会用 linux,怎么回事呢. 单击图片即可放大. 前置- linux 虚拟机的安装 在官网 / 其他地方下载 VMware. 在 noi 官网 ...
- 针对Jupter Kernel error的问题解决
首先打开Anaconda Prompt 输入jupyter kernelspec list查看安装的内核和位置 到显示的的目录下面找到 kernel.josn这个文件 修改为现在的python环境路径 ...