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.pushBackbutton加入到_children中去,到底引用计数在哪里+1操作?答案在pushBack操作中,_childrencocosRef量身定制的向量Vector<T>,这个向量只能给继承Ref的类使用

void pushBack(T object){
...
_data.push_back( object );
object->retain();
}

代码中可以看到object->retain(),对添加进来的对象引用+1操作,那么什么时候-1呢?

当我们移除场景的时候,应该释放场景中的button的。Node被移除时会调用当前的Node的父亲的removeChild函数,此函数最后会调用Nodecleanup函数,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里面的对象呢?接下来就要看DirectormainLoop函数了,这个函数在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的更多相关文章

  1. objective-C 的内存管理之-自动释放池(autorelease pool)

    如果一个对象的生命周期显而易见,很容易就知道什么时候该new一个对象,什么时候不再需要使用,这种情况下,直接用手动的retain和release来判定其生死足矣.但是有些时候,想知道某个对象在什么时候 ...

  2. 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方 ...

  3. C#内存管理解析

    前言:对于很多的C#程序员来说,经常会很少去关注其内存的释放,他们认为C#带有强大的垃圾回收机制,所有不愿意去考虑这方面的事情,其实不尽然,很多时候我们都需要考虑C#内存的管理问题,否则会很容易造成内 ...

  4. Linux内存管理解析(一) : 分段与分页机制

    背景 : 在此文章里会从分页分段机制去解析Linux内存管理系统如何工作的,由于Linux内存管理过于复杂而本人能力有限.会尽量将自己总结归纳的部分写清晰. 从实模式到保护模式的寻址方式的不同 : 1 ...

  5. Linux内存管理解析(二) : 关于Linux内存管理的大体框架

    什么是内存管理 ? 首先内存管理管理的主要对象是虚拟内存,但是虚拟内存对应的映射主要为物理内存,其次也可能通过交换空间把虚拟内存与硬盘映射起来,既然如此,那我们先了解物理内存的管理. 对于物理内存而言 ...

  6. 深入解析alloc/retain/release/dealloc实现

    首先通过GNUstep上得源码来叙述各个函数的实现(GNUstep是Cocoa框架的互换框架,二者的行为和实现方式很相似) GNUstep源码中NSObject类的alloc方法: id = obj ...

  7. linux内存管理解析1----linux物理,线性内存布局及页表的初始化

    主要议题: 1分页,分段模式及实模式 2Linux分页 3linux内存线性地址空间布局及物理内存空间布局 4linux页表初始化及代码解析 1.1.1内存寻址和保护模式 在X86平台上,内存控制单元 ...

  8. Linux内存管理解析(三) : 内核对内核空间的内存管理

    内核采用 struct page 来表示一个物理页,在其中记载了诸多物理页的属性,比如 物理页被几个线程使用(如若没有则表示该页可以释放),页对应的虚拟地址. 首先需要知道的是,分配物理页可以分为两个 ...

  9. WINDOWS页式内存管理解析

    jpg 改 rar

  10. iOS 进阶—— iOS 内存管理

    1 似乎每个人在学习 iOS 过程中都考虑过的问题 alloc retain release delloc 做了什么? autoreleasepool 是怎样实现的? __unsafe_unretai ...

随机推荐

  1. Docker 镜像库国内加速的几种方法

    概述 在国内,拉取 Docker 镜像速度慢/时不时断线/无账号导致限流等,比较痛苦. 这里提供加速/优化的几种方法. 梳理一下,会碰到以下情况: 国内下载速度慢/时不时断线:是因为网络被限制了. 没 ...

  2. Github的一个奇技淫巧

    背景 前段时间给 VictoriaLogs 提交了一个 PR: https://github.com/VictoriaMetrics/VictoriaMetrics/pull/4934 本来一切都很顺 ...

  3. .NET应用如何防止被反编译

    前言 前段时间分享了两篇关于.NET反编译相关的文章,然后文章留言区就有小伙伴提问:如何防止被反编译?因此本篇文章我们就来讲讲.NET应用如何防止被反编译..NET反编译相关的文章可以看如下文章: 4 ...

  4. pandas(进阶操作)-- 处理非数值型数据 -- 数据分析三剑客(核心)

    博客地址:https://www.cnblogs.com/zylyehuo/ 开发环境 anaconda 集成环境:集成好了数据分析和机器学习中所需要的全部环境 安装目录不可以有中文和特殊符号 jup ...

  5. 《机器人SLAM导航核心技术与实战》第1季:第6章_机器人底盘

    <机器人SLAM导航核心技术与实战>第1季:第6章_机器人底盘 视频讲解 [第1季]6.第6章_机器人底盘-视频讲解 [第1季]6.1.第6章_机器人底盘_底盘运动学模型-视频讲解 [第1 ...

  6. asp.net mvc Core 网页错误提示:An unhandled exception occurred while processing the request.处理请求时发生未处理的异常。

    网页错误提示: An unhandled exception occurred while processing the request. InvalidOperationException: The ...

  7. linux日常维护(二)

    linux启动流程 BIOS自检 启动GRUB 2 加载内核 执行systemd进程 初始化系统环境 执行/bin/login程序 (一)BIOS自检 加电POST自检(对硬件进行检测) 进行本地设备 ...

  8. 教育法学第九章单元测试MOOC

    第九章单元测试 返回 本次得分为:100.00/100.00, 本次测试的提交时间为:2020-09-06, 如果你认为本次测试成绩不理想,你可以选择 再做一次 . 1 单选(5分) 作为教师最基本的 ...

  9. 查漏补缺,这些热门开源项目你都知道么?「GitHub 热点速览」

    本期热点速览的周榜部分的项目,基本上每周都会在 GitHub Trending 见到它们的身影,因为它们实在太火了.一般来说,这些火爆的项目大家都耳熟能详,但是为了防止有些小伙伴不怎么逛 GitHub ...

  10. eclipse使用技巧和插件

    eclipse使用技巧和插件 本篇文章只列举了一部分技巧和插件,并没有包括大家都知道的快捷键和技巧,而是一些不经常用但又很方便的功能. 一,技巧 给Eclipse添加更方便的提示功能:Windows– ...