小水滴所理解的Cocos2dx内存管理机制

前端之家收集整理的这篇文章主要介绍了小水滴所理解的Cocos2dx内存管理机制前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前言Cocos2dx采用的源于Objective-c语言的引用计数机制来进行内存管理。如果接触过objective-c语言,那么引用计数机制就好理解了。在使用C++语言编程的时候,用new运算符为对象分配一块内存,当该对象不需要被使用的时候,用delete运算符释放掉该对象占用的内存。这样看起来挺不错的,为什么Cocos2dx还要采用引用计数机制呢?这是因为在开发过程中我们很容易犯一些与指针有关的错误,比如该释放内存却忘记释放内存,造成内存泄漏;然后有时候我们释放了ptr指向的那块内存,却没有将ptr置空,在不经意之间有可能会再一次释放ptr指向的内存,造成一些内存错误。那么我们就希望有这么一种机制的出现,让我们不需要花费太多的精力去想在什么时候去delete对象,所以Cocos2dx就采用了引用计数机制,可以很好的避免这样一些问题。

简述:Cocos2dx中有一个专门负责内存管理的类——Ref(后面会介绍它的源码),大部分的类都会继承这个基类。例如CCLayer,CCSprite,CCNode,CCScene

这样继承Ref的对象就会有一个引用计数(就是一个数字),当我们用new运算符构造一个继承Ref的对象的时候,会将引用计数初始化为1,调用Ref中的retain()方法使引用计数增加1,调用release()方法使引用计数减1,当引用计数为0的时候就会用delete运算符干掉该对象

Cocos2dx内存管理:

Sprite* sprite=Sprite::create("helloworld.png");
layer->addchild(sprite);
上面创建了一个Sprite对象,然后把这个对象加入到layer中,sprite就显示出来了,我们也不再需要关心什么时候去释放sprite了,sprite对象的生命周期与layer生命周期关联起来了,当layer被释放的时候,sprite也会跟着被释放。那么这是为什么呢?下面我们跟进到Sprite::create("")源码中去看看究竟。
Sprite* Sprite::create(const std::string& filename)
{
    Sprite *sprite = new (std::nothrow) Sprite();
    if (sprite && sprite->initWithFile(filename))
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return nullptr;
}
我们可以从上面源码中看出来,首先new了一个Sprite对象,然后对该对象作了一个sprite->autorelease()操作,从命名上可以看出sprite会被自动释放。那么为什么sprite对象调用了autorelease方法之后就可以自动管理内存了呢?这是因为Sprite类继承了Ref类(绝大多数类都继承了Ref),而这个autorelease方法就是Ref中的成员方法。继续跟进到Ref源码中。
class  Ref
{
public:
    /**
     * 引用计数加1
     */
    void retain();
    /**
     * 引用计数减1,减完之后如果引用计数为0,则delete ptr
     */
    void release();
    /**
     * 将对象加入到autoReleasePool中,在每一帧的最后会对当前autoReleasePool中的每一个对象调用release()方法
     */
    Ref* autorelease();
    /**
     *  get方法获取引用计数
     */
    unsigned int getReferenceCount() const;
protected:
    /**
     * 构造函数,将构造函数属性置为protected,Ref无法被实例化,必须被继承才能起作用。
     */
    Ref();
public:
    virtual ~Ref();
protected:
    // 引用计数
    unsigned int _referenceCount;
    friend class AutoreleasePool;
};
Ref类就是用来管理引用计数的,没有其他功能。一个继承自Ref类的对象调用retain()方法,引用计数加1;调用release()方法,引用计数减1;但是调用autoRelease()方法只是将指向该对象的指针加入到一个自动释放池中,并不会改变该对象的引用计数。

看完上述源码之后,我们大概就明白一些了,当new一个Sprite对象的时候,该对象的引用计数会被初始化为1,然后调用autoRelease()方法将对象加入到自动释放池中就ok了。再思考一个问题:当我们Sprite* sprite=Sprite::create("helloworld.png")创建一个Sprite对象,然后什么也不做,这个时候sprite的引用计数始终是1吗?该对象释放的时机在哪?事实上当sprite调用了autoRelease()方法后,引用计数会在每一帧的最后减1。下面接着分析。

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (_restartDirectorInNextLoop)
    {
        _restartDirectorInNextLoop = false;
        restartDirector();
    }
    else if (! _invalid)
    {
        drawScene();
     
        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

mainLoop()执行一次就是一帧,每帧的结尾都会调用一个PoolManager::getInstance()->getCurrentPool()->clear()方法,接着跟进一下clear方法

void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = true;
#endif
    for (const auto &obj : _managedObjectArray)
    {
        obj->release();
    }
    _managedObjectArray.clear();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    _isClearing = false;
#endif
}
 从源码中可以看出,遍历一遍_managerdObjectArray,对每个obj调用release()方法,使得引用计数减1,减完之后如果引用计数为0,就delete掉该对象。在clear的方法最后还会清理一遍_managedObjectArray。 
 

我们最后再来梳理一遍最先的例子:

帧开始:

1. Sprite* sprite=Sprite::create("helloworld.png"); 引用计数为1

2. layer->addChild(sprite); 引用计数为2 addChild()方法是Node中的成员方法,会增加sprite的引用计数。

帧结束: 引用计数为1 在帧结束之前有操作:PoolManager::getInstance()->getCurrentPool()->clear()。

帧结束的时候,sprite的引用计数为1,所以sprite会一直存在于内存中直到它的父类layer被释放。那么layer被释放的时候做了什么操作会使得sprite的引用计数减1呢?

在Node析构的时候,会遍历一遍Node中的_children数组,调用_children中的每一个对象的release方法,sprite的引用计数减1,最终被析构。


写的第一篇Cocos2dx文章文章中有什么问题或者不足之处希望大家提出来,共同进步。

猜你在找的Cocos2d-x相关文章