cocos2dx 3.1从零学习(四)——内存管理(错误案例分析)

本篇内容文字比较较多,但是这些都是建立在前面三章写代码特别是传值的时候崩溃的基础上的。可能表达的跟正确的机制有出入,还请指正。 如果有不理解的可以联系我,大家可以讨论一下,共同学习。

首先明确一个事实,retain和release是一一对应的,跟new和delete一样。

1.引用计数retain release

这里请参考一下引用计数的书籍,肯定说的比我讲的详细。

简单一点理解就是,对new的指针加一个计数器,每引用一次这块内存,计数就加1。在析构的时候减1,如果等于0的时候就delete这个指针并置空。

2.自动释放autolease

autorelease后的对象默认计数是1,并且autorelease的对象会被放到自动释放池里。自动释放池这里有一个需要注意的地方,自动释放池存储了当前帧所有的autorelease的对象,在帧结束时对其中所有对象release一次,处理完后这个释放池就不再拥有对这些对象的处理权,也就是说自动释放池只会最其中的对象进行一次release操作。释放的同时使用一个新的释放池存储后一帧定义的autorelease对象,如此循环下去。

精灵们create函数执行后会被放到自动释放池,释放池会在每帧结束的时候调用,对于引用计数为1的内存进行释放。如果没有其他操作比如retain或者addchild的话,那么引用计数没有增加,当前帧结束后计数减1为0后,这个指针也就不复存在了。

什么时候计数会加1?

手动调用retain使引用技术加1;

cocos2dx我所见过的create静态方法都是调用autorelease的,计数默认为1。

每引用一次,比如使用频率最多的addChild()会使其引用技术加1。

什么时候计数会减1?

手动调用release使引用技术减1;

自动释放池里的会在当前帧结束的时候减1。注意是当前帧,后面的释放池里存储的是后面帧运行时定义的autorelease对象。

如果一个场景析构,会对所有的子节点release一次,这被称为链式反应。

链式反应解释如下:我们当前运行这一个场景,场景初始化,添加了很多层,层里面有其它的层或者精灵,而这些都是 CCNode节点,以场景为根,形成一个树形结构,场景初始化之后(一帧之后),这些节点将完全 依附 (内部通过
retain) 在这个树形结构之上,全权交由树来管理,当我们 砍去一个树枝,或者将树 连根拔起,那么在它之上的“子节点”也会跟着去除(内部通过release),这便是链式反应。来自
<http://www.tairan.com/archives/4184>

错误案例:

我们在create后,如果不使用retain使引用计数加1的话,那么自动释放池会使其引用计数减1,如果在回调函数中使用addchild(sp)会崩溃。

要想解决这个问题,在create后添加使用sp->retain();来增加它的引用计数。

如下:

     auto temp = Sprite::create("CloseNormal.png");
    temp->retain();//如果注释掉会崩溃。
    auto item4 =MenuItemLabel::create(Label::createWithBMFont("fonts/futura-48.fnt","Hell"),         [=](Ref * ref){
        addChild(temp);
    });

有些人可能会使用引用的lambda表达式,如下:

    auto  temp =Sprite::create("CloseNormal.png");
    temp->retain();
    auto item4 =MenuItemLabel::create(Label::createWithBMFont("fonts/futura-48.fnt","Hell"),         [&](Ref * ref){
        addChild(temp);
    });

崩溃了!引用的话 即使retain也会崩溃,这个为什么呢?

引用的话我们使用的是temp的别名引用,也就指向指针的指针temp。当这个函数执行完的时候temp做为局部变量就会被释放。所以我们在回调函数中使用的temp已经不存在了。
如果是=赋值的话,精灵的指针会拷贝一份传到lambda表达式中,所以不会崩溃。

要想解决引用崩溃的问题,我们只要使temp不会被释放就好。所以定义为成员变量可以解决引用的lambda表达式造成的问题,大家可以尝试一下。

深入理解CC_SYNTHESIZE_RETAIN

假装我们从未学习过CC_SYNTHESIZE_RETAIN。第二篇讲过场景之间的正向传值,如果我们在主场景create一个精灵,然后赋值给下一个场景的成员变量Sprite
*sp,对于这种autorelease的变量我们应该怎么进行传值操作呢?

autorelease变量会在每一帧结束的时候计数减1进行销毁。所以我们应该对其计数加1,避免下个场景使用的时候已经被删除。

我们应该在主场景切换场景的时候这样写:

voidMainScene::Morning_0623_MemoryManage(cocos2d::Ref * ref)
{
    auto scene = MemoryManage::createScene();
    auto memLayer = (MemoryManage *)scene->getChildren().at(0);
    tmpSp =Sprite::create("coc/buildings_lowres/59.0.png");//注意斜杠的方向
    tmpSp->retain();//引用计数加1,否则当前帧结束会被销毁
    memLayer->sp = tmpSp;//如果不retain的话会被自动释放掉   在切换场景的时候会被释放掉。
   Director::getInstance()->pushScene(scene);
}

在下个场景MemoryManage定义成员变量sp的时候应该对其进行初始化,因为它是一个指针。

我们应该定义Sprite *sp = nullptr;

否则在MainScene复制的时候会崩溃,因为它的一个未知的指针,指向了内存中未知的区域。

崩溃的地方如下:

断言失败    CCASSERT(_referenceCount > 0,"reference count should greater than 0");

因为这个时候sp是一个未知的指针。

下面我们对主场景中

tmpSp
=Sprite::create("coc/buildings_lowres/59.0.png");创建的精灵的整个生命周期的引用计数进行分析。

主场景create时autorelease(1)->retain(2)->autorelease自动释放池release(1)->在子场景中被addchild(2)->子场景析构的链式反应(1)->???

请看子场景析构的时候计数还是1,这会造成内存泄露。所以我们应该在析构函数中执行一次sp->release().手动减1。

CC_SYNTHESIZE_RETAIN的出现就是为了解决上述问题,它只是把retain和release操作包装了一下。

这个时候你再去看一遍CC_SYNTHESIZE_RETAIN的源码:

#defineCC_SYNTHESIZE_RETAIN(varType, varName, funName)    private: varTypevarName; public: virtualvarType get##funName(void) const { return varName; } public: virtual voidset##funName(varType var)   {     if (varName != var)     {         CC_SAFE_RETAIN(var);         CC_SAFE_RELEASE(varName);         varName = var;     } }

调用CC_SYNTHESIZE_RETAIN来给成员变量赋值时,会对原来的变量进行一次retain操作。然后需要我们在析构函数的时候添加对应的
CC_SAFE_RELEASE(varName);

现在说一下为什么在CC_SYNTHESIZE_RETAIN中对成员变量varName执行CC_SAFE_RELEASE(varName);

varName如果被不同的变量多次赋值会怎么样?
每一次的赋值原来的变量都要做一次retain操作,如果我们直接改变了varName的值而不改变它原来指向的内存的引用计数的话,那么就会造成内存泄露。
所以每次赋值都会对原来的内存进行一次release。

总结:retain和release是一一对应的,但是我们应该使用它们的加强版。宏定义CC_SAFE_RETAIN和CC_SAFE_RELEASE。这两个可不是一一对应的。比如我们
CC_SYNTHESIZE_RETAIN定义的变量,只在析构函数中加一句CC_SAFE_RELEASE。

cocos2dx 3.1从零学习(四)——内存管理(错误案例分析)

时间: 2025-01-02 19:13:02

cocos2dx 3.1从零学习(四)——内存管理(错误案例分析)的相关文章

cocos2dx 3.1从零学习(二)——菜单、场景切换、场景传值

回顾一下上一篇的内容,我们已经学会了创建一个新的场景scene,添加sprite和label到层中,掌握了定时事件schedule.我们可以顺利的写出打飞机的主场景框架. 上一篇的内容我练习了七个新场景,每一个场景都展示不同的东西,像背景定时切换.各种字体的随机颜色和位置等.每次要切换一个场景都要修改AppDelegate中的调用代码,非常的不方便查看,这一篇我们写场景的切换.每当我们创建一个新的场景的时候只要添加对应按钮到主界面,点击即可以切换过去查看对应的效果.这个有点类似官方提供的cppt

cocos2dx 3.1从零学习(一)——入门篇(一天学会打飞机)

我们有C++基础,学习引擎总是急于求成,想立马做出一款简单的游戏给朋友玩.但是我们往往看了很多资料却一直不知道如何下手去写,有时候只要能走出第一步我们就会游刃有余,但是眼高手低的我们不是大神,需要有人指引一下.这里我就写一下我是如何入门学习cocos2dx3.1的,给大家参考一下. 如果你想第一天就写出微信打飞机,请耐心去阅读.我也是一个菜鸟,博客难免粗糙和出错,请大家谅解.加油吧! 我们创建工程后总会自带一个HelloWorld类,短短的几行代码就出来了一个游戏的雏形,请问我们真的理解它了吗?

cocos2dx 3.1从零学习(三)——Touch事件(回调,反向传值)

第三讲 Touch 前面两篇我们学习的内容,足够我们做一款简单的小游戏.也可以说,我们已经入门了,可以蹒跚的走路了. 本篇将讲解cocos2dx中很重要的touch回调机制.你肯定记得第一章做定时器时间的时候用过CC_CALLBACK_1宏定义,它让我们回调一个只有一个形参的函数来执行定时操作. 回调函数的实现(Lambda表达式) 学习本篇前请仔细学习一下C++11的特性,std::function和lambda表达式.C++11还引入了很多boost库的优秀代码,使我们在使用的时候不必再加b

cocos2dx 3.1从零学习(五)——动画

动画是游戏中最重要的表现部分,本篇只是初步学习大部分动画的使用方法,没有什么原理性的东西,但是例子有很多,如果有不熟的地方要练一下. 特别是Spawn和Sequence的组合使用,什么时候使用Spawn,什么使用使用Sequence,怎么在它们之间相互嵌套对方.用光你的脑细胞尽力的去折磨这几个函数吧!做出尽可能多的绚丽的组合! 动画方法 动画的方法都在Node中,它的子类包括Layer.Sprite.MenuItem等. runAction 启动动画 stopAction 停止动画 stopAc

cocos2dx 3.1从零学习(六)——CocosStudio(VS2013工程导入及环境设置)

导入libCocosStudio.libExtensions.libGUI 新建的工程如下图: 添加现有项目 右键解决方案,如下操作: 分别添加以下三个项目: (工程路径)\cocos2d\cocos\ui\proj.win32\libGUI.vcxproj (工程路径)\cocos2d\cocos\editor-support\cocostudio\proj.win32\libCocosStudio.vcxproj (工程路径)\cocos2d\extensions\proj.win32\li

JVM学习笔记-内存管理

第一章 内存分配 1. 内存区域. 方法区和堆(线程共享),程序计数器 , VM栈 和 本地方法栈(线程隔离). 1) java虚拟机栈:线程私有.描述的是java方法执行的内存模型:栈帧,用户存储 局部变量表,操作数栈,动态链接,方法出口等信息. 局部变量表在编译时即可完全确定!如果线程请求的栈深度大于 规定的深度,StackOverflowError. 2) 本地方法栈,类似. 3)堆:垃圾收集器管理的主要区域.线程共享. 4)方法区: 各个线程共享.存储:加载的类信息,常量,静态变量,即时

cocos2dx 启动过程详解二:内存管理和回调

在上一篇的第二部分中,我们有一句代码待解释的: // Draw the Scene void CCDirector::drawScene(void) { -- //tick before glClear: issue #533 if (! m_bPaused) //暂停 { m_pScheduler->update(m_fDeltaTime);   //待会会解释这里的内容 } -- } 这里是一个update函数,经常会写像this->schedule(schedule_selector(X

ArcGIS for Desktop入门教程_第四章_入门案例分析 - ArcGIS知乎-新一代ArcGIS问答社区

原文:ArcGIS for Desktop入门教程_第四章_入门案例分析 - ArcGIS知乎-新一代ArcGIS问答社区 1 入门案例分析 在第一章里,我们已经对ArcGIS系列软件的体系结构有了一个全面的了解,接下来在本章中,将通过一个案例来熟悉ArcGIS for Desktop的使用,从解决问题的过程中,逐渐适应ArcGIS桌面的界面和操作方式. 本章的练习数据是一个住宅小区的简单平面示意图,需要在已有的基础上把楼房的轮廓补充完整,并加以整饰,完成一幅地图. 1.1 打开地图文档并浏览

Linux-0.11内核内存管理get_free_page()函数分析

/* *Author : DavidLin*Date : 2014-11-11pm*Email : [email protected] or [email protected]*world : the city of SZ, in China*Ver : 000.000.001*history : editor time do 1)LinPeng 2014-11-11 created this file! 2)*/Linux-0.11内存管理模块是源代码中比较难以理解的部分,现在把笔者个人的理解