Cocos2d-x引擎学习笔记(五)—— 内存管理 源码分析

cocos2d-x版本:3.17.2

运行环境:Visual Studio 2017

解决方案配置:Debug Win32


1. Ref类

class CC_DLL Ref
{
public:
    // 增加一次引用计数
    void retain();

    // 减少一次引用计数
    void release();

    // 自动释放
    Ref* autorelease();
    
    unsigned int getReferenceCount() const;

protected:
    Ref();

public:
    virtual ~Ref();

protected:
    // 用来记录引用次数的变量值
    unsigned int _referenceCount;
	// 自动释放对象池
    friend class AutoreleasePool;
};

Ref这个类使用_referenceCount来记录引用次数的变量值。Ref的构造函数声明为 protected 的访问权限,那么这个Ref类是不可以被直接实例化的 只能有子类来实例化这个对象。

// 构造函数
Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
#if CC_ENABLE_SCRIPT_BINDING
, _luaID (0)
, _scriptObject(nullptr)
, _rooted(false)
#endif
{
#if CC_ENABLE_SCRIPT_BINDING
    static unsigned int uObjectCount = 0;
    _ID = ++uObjectCount;
#endif
    
#if CC_REF_LEAK_DETECTION
    trackRef(this);
#endif
}

当Ref被创建的时候,引用计数自动设为1。CC_ENABLE_SCRIPT_BINDING宏定义表示支持脚本绑定,这里定义了一个静态变量uObjectCount用来记录创建的对象数量,当Ref的子类创建对象的时候,基类的Ref的构造函数里 uobjectCount就会自增1,这也使所有对象都有唯一的_ID值不会重复。

// 析构函数
Ref::~Ref()
{
#if CC_ENABLE_SCRIPT_BINDING
    ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
    if (pEngine != nullptr && _luaID)
    {
        // if the object is referenced by Lua engine, remove it
        pEngine->removeScriptObjectByObject(this);
    }
#if !CC_ENABLE_GC_FOR_NATIVE_OBJECTS
    else
    {
        if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
        {
            pEngine->removeScriptObjectByObject(this);
        }
    }
#endif // !CC_ENABLE_GC_FOR_NATIVE_OBJECTS
#endif // CC_ENABLE_SCRIPT_BINDING


#if CC_REF_LEAK_DETECTION
    if (_referenceCount != 0)
        untrackRef(this);
#endif
}

对象销毁的时候,会去通知脚本管理器ScriptEngineManager消除对象。

// Retain函数 增加一次引用计数
void Ref::retain()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    ++_referenceCount;
}

// Release函数,减少一次引用计数。当引用计数为0的时候,删除该对象
void Ref::release()
{
    CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
    --_referenceCount;

    if (_referenceCount == 0)
    {
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
        auto poolManager = PoolManager::getInstance();
        if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
        {
            CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
        }
#endif

#if CC_ENABLE_SCRIPT_BINDING
        ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
        if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
        {
            pEngine->removeObjectProxy(this);
        }
#endif // CC_ENABLE_SCRIPT_BINDING

#if CC_REF_LEAK_DETECTION
        untrackRef(this);
#endif
        delete this;
    }
}

分析代码,Release函数就干了两件事

  1. 当调用release方法时,先减少一次此对象的引用计数
  2. 当引用计数referenceCount值为0的时候表明其它地方不再需要这个对象了,那么就 delete this 销毁这个对象
Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

autorelease函数则没有减少引用次数,而是直接调用PoolManager的内存管理池来进行处理。

现查看PoolManager类

// 管理所有的AutoreleasePool的管理器
class CC_DLL PoolManager
{
public:

    CC_DEPRECATED_ATTRIBUTE static PoolManager* sharedPoolManager() { return getInstance(); }
    static PoolManager* getInstance();
    
    CC_DEPRECATED_ATTRIBUTE static void purgePoolManager() { destroyInstance(); }
    static void destroyInstance();
    
    // 获取当前AutoreleasePool
    AutoreleasePool *getCurrentPool() const;

    bool isObjectInPools(Ref* obj) const;

    friend class AutoreleasePool;
    
private:
    PoolManager();
    ~PoolManager();
    
    void push(AutoreleasePool *pool);
    void pop();
    
    static PoolManager* s_singleInstance;
    
    std::vector<AutoreleasePool*> _releasePoolStack;
};


// 用于管理自动释放对象的池
class CC_DLL AutoreleasePool
{
public:
    AutoreleasePool();
    
    AutoreleasePool(const std::string &name);
    
    ~AutoreleasePool();

    void addObject(Ref *object);

    void clear();
    
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    // 判断自动释放池是否正在执行`clear`操作。
    bool isClearing() const { return _isClearing; };
#endif
    
    // 检查自动释放池是否包含指定的对象。
    bool contains(Ref* object) const;
    
    void dump();
    
private:
    std::vector<Ref*> _managedObjectArray;
    std::string _name;
    
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
    /**
     *  The flag for checking whether the pool is doing `clear` operation.
     */
    bool _isClearing;
#endif
};

二者的关系:PoolManager用栈的形式管理AutoreleasePool,从其构造函数与析构函数可以看出,每新创建一个AutoreleasePool就push进栈,而销毁一个AutoreleasePool就pop出栈。每一次通过PoolManager::tCurrentPool()函数获取的都是栈顶当前的AutoreleasePool.

其中主要看AutoreleasePool的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
}

该函数遍历了对象列表里面的每一个Ref对象,调用它的release方法,减少对象的引用次数,然后检查那些引用次数为0的对象,将其销毁。而何时调用这个clear函数呢,实际上在我们Director的mainLoop中每一次循环结束都会去释放一次自动管理的对象

cocos很多类型继承了 Ref,这是因为为了放到内存管理器里面统一管理,并且可以在类型创建的时候就已经使用到了autorelease来做内存释放。新创建的对象直接加入到自动的内存管理里面,来实现自动释放,这样避免了我们到处去想着delete,更加安全方便。

你可能感兴趣的:(Cocos2d引擎)