zoukankan      html  css  js  c++  java
  • Cocos2d之“引用计数”内存管理机制实现解析

    一、引言

    本文主要分析cocos2d游戏开发引擎的引用计数内存管理技术的实现原理。建议读者在阅读本文之前阅读笔者之前一篇介绍如何使用cocos2d内存管理技术的文章——《Cocos2d之Ref类与内存管理使用详解》

    二、相关概念

    引用计数

    引用计数是计算机编程语言的一种内存管理技术,是指将资源(对象、内存或者磁盘空间等)的被引用计数保存起来,当引用计数变为零时就将资源释放的过程。使用引用计数技术可以实现自动内存管理的目的。

    当实例化一个类时,对象的引用计数为1,在其他对象需要持有这个对象时,就把该对象的引用计数加1,需要解除对该对象的持有关系时,就将该对象的引用计数碱1,直至对象的引用计数为0,对象的内存就被立即释放。

    内存管理池

    就是一个AutoreleasePool对象。

    对象池

    内存管理池中一个存储所有Ref对象的结构。

    三、实现过程详解

    Ref类

    《Cocos2d之Ref类与内存管理使用详解》一问中已经详细介绍了Ref类的使用,这里就不再赘述了,直接看Ref的源码吧。

    class CC_DLL Ref
    {
    public:
        /**
         * Retains the ownership.
         *
         * This increases the Ref's reference count.
         *
         * @see release, autorelease
         * @js NA
         */
        void retain();
    
        /**
         * Releases the ownership immediately.
         *
         * This decrements the Ref's reference count.
         *
         * If the reference count reaches 0 after the descrement, this Ref is
         * destructed.
         *
         * @see retain, autorelease
         * @js NA
         */
        void release();
    
        /**
         * Releases the ownership sometime soon automatically.
         *
         * This descrements the Ref's reference count at the end of current
         * autorelease pool block.
         *
         * If the reference count reaches 0 after the descrement, this Ref is
         * destructed.
         *
         * @returns The Ref itself.
         *
         * @see AutoreleasePool, retain, release
         * @js NA
         * @lua NA
         */
        Ref* autorelease();
    
        /**
         * Returns the Ref's current reference count.
         *
         * @returns The Ref's reference count.
         * @js NA
         */
        unsigned int getReferenceCount() const;
    
    protected:
        /**
         * Constructor
         *
         * The Ref's reference count is 1 after construction.
         * @js NA
         */
        Ref();
    
    public:
        /**
         * @js NA
         * @lua NA
         */
        virtual ~Ref();
    
    protected:
        /// count of references
        unsigned int _referenceCount;
    
        friend class AutoreleasePool;
    
    #if CC_ENABLE_SCRIPT_BINDING
    public:
        /// object id, ScriptSupport need public _ID
        unsigned int        _ID;
        /// Lua reference id
        int                 _luaID;
        /// scriptObject, support for swift
        void* _scriptObject;
    #endif
    
        // Memory leak diagnostic data (only included when CC_USE_MEM_LEAK_DETECTION is defined and its value isn't zero)
    #if CC_USE_MEM_LEAK_DETECTION
    public:
        static void printLeaks();
    #endif
    };

    成员变量 _referenceCount 就是所谓的引用计数。下面看成员方法的具体实现。

    Ref::Ref()
    : _referenceCount(1) // when the Ref is created, the reference count of it is 1
    {
        ....
    }
    
    void Ref::retain()
    {
        CCASSERT(_referenceCount > 0, "reference count should greater than 0");
        ++_referenceCount;
    }
    
    void Ref::release()
    {
        CCASSERT(_referenceCount > 0, "reference count should greater than 0");
        --_referenceCount;
    
        if (_referenceCount == 0)
        {
            ....
            delete this;
        }
    }
    
    Ref* Ref::autorelease()
    {
        PoolManager::getInstance()->getCurrentPool()->addObject(this);
        return this;
    }

    从源码可以知道,该类在实例化的时候直接把引用计数 _referenceCount 置1,retain和release方法就是负责增减引用计数 _referenceCount 而已,autorelease将该对象交给内存管理池 AutoreleasePool 管理。

    下面看一下如何利用这几个接口来创建一个Ref对象。

    #define CREATE_FUNC(__TYPE__) 
    static __TYPE__* create() 
    { 
        __TYPE__ *pRet = new __TYPE__(); 
        if (pRet && pRet->init()) 
        { 
            pRet->autorelease(); 
            return pRet; 
        } 
        else 
        { 
            delete pRet; 
            pRet = NULL; 
            return NULL; 
        } 
    }

    这个宏定义了cocos2d里=框架中众多Ref子类(如Scene、Layer)的静态方法create(void)的实现。当创建Ref子类对象时,构造函数将 _referenceCount 置1,然后调用对象的autorelease函数把对象交由当前的内存池管理。因为对象一开始 _referenceCount 就为1,那如果对象创建后没有被持有也没有被调用release函数,会不会造成内存泄露呢?不会的,因为autorelease会在内存管理池销毁后将对象的 _referenceCount 减 1,以抵消创建对象时增加的引用计数,如果此时引用计数为0,那么对象就会被立即回收。

    PoolManager类

    Ref类的autorelease函数其实已经将内存管理的工作交给PoolManager完成了,而自己只是管理引用计数而已。下面看PoolManager类的声明。

    class CC_DLL PoolManager
    {
    public:
        /**
         * @js NA
         * @lua NA
         */
        CC_DEPRECATED_ATTRIBUTE static PoolManager* sharedPoolManager() { return getInstance(); }
        static PoolManager* getInstance();
        
        /**
         * @js NA
         * @lua NA
         */
        CC_DEPRECATED_ATTRIBUTE static void purgePoolManager() { destroyInstance(); }
        static void destroyInstance();
        
        /**
         * Get current auto release pool, there is at least one auto release pool that created by engine.
         * You can create your own auto release pool at demand, which will be put into auto releae pool stack.
         */
        AutoreleasePool *getCurrentPool() const;
    
        bool isObjectInPools(Ref* obj) const;
    
        /**
         * @js NA
         * @lua NA
         */
        friend class AutoreleasePool;
        
    private:
        PoolManager();
        ~PoolManager();
        
        void push(AutoreleasePool *pool);
        void pop();
        
        static PoolManager* s_singleInstance;
        
        std::vector<AutoreleasePool*> _releasePoolStack;
    };

    从源码可以看出,PoolManager类被设置成单例模式。单例 s_singleInstance 持有一个管理 AutoreleasePool的 _releasePoolStack 栈。AutoreleasePool类是真正的内存管理池,其实现过程会在下文谈到。_releasePoolStack 栈中至少会有一个由引擎创建的AutoreleasePool对象。PoolManager类提供了获取当前内存管理池currentPool和向对象管理池查询对象的接口。注意,push和pop函数被限制为private,意味着开发者不能够之间向PoolManager单例中添加或者删除AutoreleasePool对象管理池。push和pop方法被AutoreleasePool使用的,也就是说AutoreleasePool创建后将自己添加到 _releasePoolStack 栈中。下面分析PoolManager的实现过程。接下来看PoolManager的实现。

    PoolManager* PoolManager::s_singleInstance = nullptr;
    
    PoolManager* PoolManager::getInstance()
    {
        if (s_singleInstance == nullptr)
        {
            s_singleInstance = new (std::nothrow) PoolManager();
            // Add the first auto release pool
            new AutoreleasePool("cocos2d autorelease pool");
        }
        return s_singleInstance;
    }
    
    void PoolManager::destroyInstance()
    {
        delete s_singleInstance;
        s_singleInstance = nullptr;
    }
    
    PoolManager::PoolManager()
    {
        _releasePoolStack.reserve(10);
    }
    
    PoolManager::~PoolManager()
    {
        CCLOGINFO("deallocing PoolManager: %p", this);
        
        while (!_releasePoolStack.empty())
        {
            AutoreleasePool* pool = _releasePoolStack.back();
            
            delete pool;
        }
    }
    
    
    AutoreleasePool* PoolManager::getCurrentPool() const
    {
        return _releasePoolStack.back();
    }
    
    bool PoolManager::isObjectInPools(Ref* obj) const
    {
        for (const auto& pool : _releasePoolStack)
        {
            if (pool->contains(obj))
                return true;
        }
        return false;
    }
    
    void PoolManager::push(AutoreleasePool *pool)
    {
        _releasePoolStack.push_back(pool);
    }
    
    void PoolManager::pop()
    {
        CC_ASSERT(!_releasePoolStack.empty());
        _releasePoolStack.pop_back();
    }

    从源码不难看出,PoolManager持有一个存储所有所有对象管理池的栈 _releasePoolStack,PoolManger无非就是实现了对 releasePoolStack 栈的管理而已,内存管理的工作由栈中不同的AutoreleasePool对象完成。

    AutoreleasePool类

    AutoreleasePool对象就是内存管理池。下面看AutoreleasePool类的声明。

    class CC_DLL AutoreleasePool
    {
    public:
        /**
         * @warn Don't create an auto release pool in heap, create it in stack.
         * @js NA
         * @lua NA
         */
        AutoreleasePool();
        
        /**
         * Create an autorelease pool with specific name. This name is useful for debugging.
         */
        AutoreleasePool(const std::string &name);
        
        /**
         * @js NA
         * @lua NA
         */
        ~AutoreleasePool();
    
        /**
         * Add a given object to this pool.
         *
         * The same object may be added several times to the same pool; When the
         * pool is destructed, the object's Ref::release() method will be called
         * for each time it was added.
         *
         * @param object    The object to add to the pool.
         * @js NA
         * @lua NA
         */
        void addObject(Ref *object);
    
        /**
         * Clear the autorelease pool.
         *
         * Ref::release() will be called for each time the managed object is
         * added to the pool.
         * @js NA
         * @lua NA
         */
        void clear();
    
        
        /**
         * Checks whether the pool contains the specified object.
         */
        bool contains(Ref* object) const;
    
        /**
         * Dump the objects that are put into autorelease pool. It is used for debugging.
         *
         * The result will look like:
         * Object pointer address     object id     reference count
         *
         */
        void dump();
        
    private:
        /**
         * The underlying array of object managed by the pool.
         *
         * Although Array retains the object once when an object is added, proper
         * Ref::release() is called outside the array to make sure that the pool
         * does not affect the managed object's reference count. So an object can
         * be destructed properly by calling Ref::release() even if the object
         * is in the pool.
         */
        std::vector<Ref*> _managedObjectArray;
        std::string _name;
        
    };

    从源码可以看出,AutoreleasePool内存管理池中只有一个Ref对象池 _managedObjectArray,AutoreleasePool类提供里往 _managedObjectArray 对象池添加对象、查询对象和清空对象池的接口。AutoreleasePool内存管理池自行维护 _managedObjectArray 对象池。

    AutoreleasePool::AutoreleasePool()
    : _name("")
    {
        _managedObjectArray.reserve(150);
        PoolManager::getInstance()->push(this);
    }
    
    AutoreleasePool::AutoreleasePool(const std::string &name)
    : _name(name)
    {
        _managedObjectArray.reserve(150);
        PoolManager::getInstance()->push(this);
    }
    
    AutoreleasePool::~AutoreleasePool()
    {
        CCLOGINFO("deallocing AutoreleasePool: %p", this);
        clear();
        
        PoolManager::getInstance()->pop();
    }
    
    void AutoreleasePool::addObject(Ref* object)
    {
        _managedObjectArray.push_back(object);
    }
    
    void AutoreleasePool::clear()
    {
        for (const auto &obj : _managedObjectArray)
        {
            obj->release();
        }
        _managedObjectArray.clear();
    }
    
    bool AutoreleasePool::contains(Ref* object) const
    {
        for (const auto& obj : _managedObjectArray)
        {
            if (obj == object)
                return true;
        }
        return false;
    }
    
    void AutoreleasePool::dump()
    {
        CCLOG("autorelease pool: %s, number of managed object %d
    ", _name.c_str(), static_cast<int>(_managedObjectArray.size()));
        CCLOG("%20s%20s%20s", "Object pointer", "Object id", "reference count");
        for (const auto &obj : _managedObjectArray)
        {
            CC_UNUSED_PARAM(obj);
            CCLOG("%20p%20u
    ", obj, obj->getReferenceCount());
        }
    }

    从AutoreleasePool的构造函数可知,AutoreleasePool内存管理池创建后会将自己添加到 PoolManager 的 _releasePoolStack 栈中,成为PoolManager的currentPool。AutoreleasePool的析构函数调用clear函数,clear函数会将调用对象池中每个对象的release函数,如果对象的引用计数为0则将对象释放,到这一步就完成了Ref类的autorelease函数了所有功能。

    四、总结

    看到这里,读者应该明白了cocos2d的引用计数内存管理技术是由AutoreleasePool、PoolManager和Ref三个类协助完成的。所有Ref的子类,都能够使用这套内存管理机制。AutoreleasePool(内存管理池)中有个对象池,存放了所有需要被管理的Ref对象。cocos2d引擎至少有一个默认的内存管理池,开发者也可以自定义和创建自己的内存管理池。PoolManager用一个栈结构管理所有的内存管理池。

  • 相关阅读:
    UVa 116 单向TSP(多段图最短路)
    POJ 1328 Radar Installation(贪心)
    POJ 1260 Pearls
    POJ 1836 Alignment
    POJ 3267 The Cow Lexicon
    UVa 1620 懒惰的苏珊(逆序数)
    POJ 1018 Communication System(DP)
    UVa 1347 旅行
    UVa 437 巴比伦塔
    UVa 1025 城市里的间谍
  • 原文地址:https://www.cnblogs.com/chenshi/p/4084067.html
Copyright © 2011-2022 走看看