cocos2dx的内存管理机制

今天看了一下cocos2dx的http://www.zaojiahua.com/tag/%e5%86%85%e5%ad%98%e7%ae%a1%e7%90%86机制,有些地方不太好理解搞了挺长的时间,现在感觉自己理解的差不多了,赶快写下自己的思路和互联网的广大朋友分享,如果你发现有错误的地方或者不理解的地方欢迎指正!首先我们必须说一下c++中变量的内存空间的分配问题,我们在c++中写一个类,可以在栈上分配内存空间也可以使用new在堆上分配内存空间,如果类对象是在栈上分配的内存空间,这个内存空间的管理就不是我们的事了,但如果是在堆上分配的内存空间,当然需要我们来手动的delete了!cocos2dx采用的是在堆上分配内存空间,想想看你在写程序的时候对于cocos2dx中的类是不是大多数都是通过工厂方法获得的一个指针,你见过在栈上分配内存空间的情况吗?所以问题来了,既然在堆上分配内存空间,那么如何管理这个内存空间,什么时候应该释放就是个问题了!在程序中,当我们创建了一个对象的时候,这块内存空间经常是被不同的对象引用,如果删除的早了,有对象还在引用这块内存空间那么程序必然要崩溃!所以cocos2dx引入了引用计数这个http://www.zaojiahua.com/tag/%e5%86%85%e5%ad%98%e7%ae%a1%e7%90%86机制。
当我们在堆上分配一块内存空间的时候,这个对象的引用计数就是1,当有对象要引用这块内存空间的时候,这个引用计数就增加1,当有对象不再引用这块内存的时候引用计数就减1,当这个引用计数减为0的时候就使用delete删除掉这块内存,这样就做到了当有对象引用的时候会正常的访问这块内存,引用完毕也可以正常的回收。先来看一下如下的代码,有关引用计数的问题就会很清楚了!

//对象创建的时候引用计数被设置为1,这个是在它的构造函数中完成的,它会先调用父类CCOjbect的构造函数
    //CCObject的构造函数如下所示
    //CCObject::CCObject(void)
    //, m_uReference(1) {}// when the object is created, the reference count of it is 1
    CCSprite * sprite = new CCSprite();

    CCLog("retain count:%d",sprite->retainCount());

    //调用retain方法的时候引用计数增加1
    sprite->retain();
    CCLog("retain count:%d",sprite->retainCount());

    //调用release方法的时候引用计数减一,当这个引用计数减为0的时候,在release方法中会delete掉这个对象
    sprite->release();
    CCLog("retain count:%d",sprite->retainCount());

    //当我们调用autorelease方法的时候会调用这段代码
    //CCPoolManager::sharedPoolManager()->addObject(this);
    //调用autorelease方法的时候对象会被放到自动回收池中,这个自动回收池在每帧结束的时候会调用一次对象的release方法
    sprite->autorelease();
    CCLog("retain count:%d",sprite->retainCount());


```


上边的代码有一处不是很好的理解,就是大家经常说的自动回收机制,也就是上边的autorelease方法,这个方法到底为我们做了什么,有什么作用,我们需要好好的搞清楚!首先需要澄清的一个概念就是帧,我们经常的说每秒多少多少帧,其实这个帧需要多少时间不是固定的,这个需要看每帧我们需要做多少事情,如果没一帧我们需要渲染很多的东西,那这一帧执行的时间当然就会很长的,游戏显得就会很卡,这个时候每秒的帧率就会下降的,所以不是时间决定的帧率,而是帧影响的时间!这个自动回收池就是在每帧结束的时候起作用的,在游戏的每一帧都会有一个大的循环,在一帧开始之前,系统建立了一个内存回收池,在这一帧的过程中,当我们调用了autorelease方法以后,我们的对象就会放到这个内存回收池中,当一帧结束的时候这个内存回收池就会释放掉,这个时候在内存回收池中的对象就会被release一下,也就是说引用计数就会减一,如果这个时候引用计数为0,就会删除对象了。如果引用计数不为0的话对象是不会被删除的,下一帧开始的时候系统又会创建一个内存回收池,这个时候在上一次添加的对象这个时候是不会重新添加到这个内存回收池中的,在这个内存回收池中的对象是你在这一帧中调用了autorelease函数的对象。好了,内存回收池我觉的我已经说清楚了。下面我们来看一下,通常我们的代码都是怎么写的,来看看这个自动回收机制怎么就做到自动回收了!



//在create的时候调用了sprite的autorelease方法
CCSprite * sprite = CCSprite::create("HelloWorld.png");
CCLog("retain count:%d",sprite->retainCount()); //retain 1
this->addChild(sprite);
CCLog("retain count:%d",sprite->retainCount()); //retain 2



```

首先我们需要分析一下上边的代码,调用了create工厂方法以后,内部的实现是先new一个CCSprite的对象,这个时候引用计数加1,然后调用autorelease方法,将这个对象放到了自动回收池中,因为这一帧还没有结束,当然引用计数就还是1,所以打印的结果就是1,当我们调用addChild的时候,传入这个CCSprite对象,这个时候在当前层接受了这个对象以后会把它的引用计数加一,表明当前层正在使用这块内存空间,所以现在的retain就是2了。当这一帧结束的时候自动回收池会将对象的引用计数-1,所以现在就只有CCLayer在引用这个对象了,当CCLayer析构的时候,它会调用这个对象的release方法,这个时候当然就会删除了这个CCSprite对象了。所以什么是自动回收机制呢,自动就是在这一帧结束的时候将对象开始new的时候加的那个引用计数减掉,而让引擎中持有对象引用的其他类去管理这个对象,当持有者析构的时候就删除引用,引擎中的类负责retain和release,这个也算是自动吧!下面我们通过俩个例子来理解一下这个内存管理机制。


bool HelloWorld::init()
{

    if ( !CCLayer::init() )
    {
        return false;
    }

    CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
            "CloseNormal.png",
            "CloseSelected.png",
            this,
            menu_selector(HelloWorld::menuCloseCallback));

    CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);

    this->addChild(pMenu, 1);

    this->m_sprite = CCSprite::create("HelloWorld.png");
    CCLog("%d",this->m_sprite->retainCount());

    return true;
}
//按钮的响应事件
void HelloWorld::menuCloseCallback(CCObject* pSender)
{
    CCLog("%d",this->m_sprite->retainCount());
    this->m_sprite->getPosition();
}



```

好了,运行程序我们会发现程序崩溃了,我们来分析一下。当我们创建了CCSprite对象的时候引用计数为1,并且该对象被放到了内存回收池中,在这一帧以后就会release对象一次,这个时候引用计数为0的对象当然就被释放了,我们按下按钮去调用这个对象,内存已经释放掉了,程序能不崩溃吗?下面我们来将代码改成如下的这样,看看效果。 


this->m_sprite = CCSprite::create("HelloWorld.png");
    CCLog("%d",this->m_sprite->retainCount());
    this->m_sprite->retain();
    CCLog("%d",this->m_sprite->retainCount());



```

这个时候运行起来程序,在按钮的函数调用的时候你会看到输出的引用计数都是1,因为我们已经手动的retain了一下这个对象,虽然自动回收池将它release了一下,但是它的引用计数任然为1。既然我们retain了,所以在层析构的时候记得要release啊。这样内存才不会泄露!还有一个就是CCArray的例子,下面来看一下。


bool HelloWorld::init()
{
    bool bRet = false;
    do
    {
        //////////////////////////////////////////////////////////////////////////
        // 父类初始化
        //////////////////////////////////////////////////////////////////////////

        CC_BREAK_IF(! CCLayer::init());
        CCSprite* bomb1 = CCSprite::create("CloseNormal.png");
        CCSprite* bomb2 = CCSprite::create("CloseNormal.png");
        CCSprite* bomb3 = CCSprite::create("CloseNormal.png");
        CCSprite* bomb4 = CCSprite::create("CloseNormal.png");
        CCSprite* bomb5 = CCSprite::create("CloseNormal.png");
        CCSprite* bomb6 = CCSprite::create("CloseNormal.png");

        addChild(bomb1,1);
        addChild(bomb2,1);
        addChild(bomb3,1);
        addChild(bomb4,1);
        addChild(bomb5,1);
        addChild(bomb6,1);

        m_pBombsDisplayed = CCArray::create(bomb1,bomb2,bomb3,bomb4,bomb5,bomb6,NULL);

        this->scheduleUpdate();

        bRet = true;
    } while (0);

    return bRet;
}

void HelloWorld::update(ccTime dt)
{
    refreshData();
}

void HelloWorld::refreshData()
{
    m_pBombsDisplayed->objectAtIndex(0)->setPosition(cpp(100,100));
}



```

其实这个原理和上边的例子是差不多的,我们创建了一个CCArray的对象,这个时候同样是没有添加到其他的层中的,所以在这一帧结束的时候就会将它的引用计数减1变成0,所以,再次使用的时候肯定就会出错了!在使用cocos2dx的内存管理的时候如果我们是通过工厂的方法创建的,并且add到了其他的层中的时候这个时候我们多数是不用担心的,但是如果你是通过new的方法创建的CCObject子类的对象,这个时候记住要在析构的时候release这个对象,如果在使用过程中你retain了对象,同样记住要release。本人总结的是,当我们调用函数传递CCObject子类对象的时候,在接受的时候我们都应该去retain一下,这样代表的是我要引用这块内存空间,什么时候你不再引用这块内存空间了,就release一下。其他的各种问题当你遇到的时候就想一下它的原理,就会明白的。最后的最后欢迎广大网友给我留言,我们共同探讨cocos2dx的问题!

最后以上内容转载自: http://www.zaojiahua.com/memory-management.html,由小塔原创,更多cocos2dx的文章请访问http://www.zaojiahua.com。

感谢 小塔的分享~:877:,一直搞不清楚cocos2d-x自动内存管理,retain,release,autorelease的童鞋可以参考此文哦。

:875:好像人气不高。

不是一开始new的时候是0的么,当autorelease的时候才变成1 :9:

其实学习C++的就知道,只有了解智能指针autoptr 和 smartptr,就容易理解垃圾回收和引用计数机制了。

//对象创建的时候引用计数被设置为1,这个是在它的构造函数中完成的,它会先调用父类CCOjbect的构造函数
//CCObject的构造函数如下所示
//CCObject::CCObject(void)
//, m_uReference(1) {}// when the object is created, the reference count of it is 1

看看这段代码亲,是不是明白了!

那 CC_SAFEDELTE什么时候用呢?假如自己写的类new完之后不使用autorelease

new出来以后引用计数是1,然后你自己写的类要引用的时候记住retain,不用了release就行,本类中不用的时候也release。看一下例子就能明白。

谢谢了, 老实说 不能说完全很明白, 晚上抽时间再看看

:14: :14: 顶楼主,写的不不错!

支持小塔,我是么么牛

学习了,很好

强烈不推荐同时使用retain,release,autorelease。强烈建议再使用了autorelease之后不要使用retain,release!!!!

— Begin quote from ____

引用第12楼佐耳云儿于2014-04-09 11:22发表的 :
强烈不推荐同时使用retain,release,autorelease。强烈建议再使用了autorelease之后不要使用retain,release!!!! http://www.cocoachina.com/bbs/job.php?action=topost&tid=195219&pid=921946

— End quote

CCArray想一直使用不retain不行啊

小塔,有个问题.
我是这么总结的.
分配内存时(只要继承了CCObject) 那么 默认引用为1
当我们create或new CCObject的子类, 那么他会自动把他加入autorelease();
然后你也说,autorelease()是每帧引用-1,
但是我发现 我长期要使用一个子类 把他retain(),也就是引用+1,
那么 如果他本身就在autorelease()里, 那么理论上 过几帧应该就引用为0被delete了吧
但是我发现我只要retain()一次, 那么如果不调用release(),那么 他是不会被释放的.
所以 我想问, 是不是retain()的同时,其实已经把这个对象从autorelease()移除了?
所以不会每帧后 引用-1

好吧 上面说的有点错 ,
autorelease()是每帧检测对象为0时则delete,
其中对象引用分配的内存空间时+!,
不再引用则-1 , 那么 m_sprite这种create()后理论上引用为1,
他什么时候被delete导致溢出呢

和oc里的引用计数一样嘛,啥时有ARC:3:

3.0里CCObject改为Ref了,谁来介绍一下?

关于内存有个地方我没搞懂
Sprite *sp=Sprite::create(“CloseNormal.png”);
log(“sp re %d”,sp->getReferenceCount());// 1
addChild(sp);
log(“sp re %d”,sp->getReferenceCount());// 2
sp->removeFromParent();
log(“sp re %d”,sp->getReferenceCount());// 1

如果我现在不需要sp了 想sp立即为null 计数为0 应该怎么做呢

所以3.0消灭了它:2::2::2::2::2::2: