zoukankan      html  css  js  c++  java
  • CocosUI的内存管理

    寒假回家期间,在家下了一份cocos源码,在阅读的过程中也整理一下cocos的架构设计和实现,也算是为国隔离。
    这次先讲一讲cocos对于UI元素的内存管理机制。之后本专栏还会写一些cocos其他模块的解析,例如渲染、事件分发等。
    众所周知,C++是一种比较底层的语言,由于它目前还不支持垃圾回收机制,因此在堆上分配一个对象之后,必须在代码逻辑中由delete回收,否则就会导致内存泄漏问题。尤其对于游戏引擎来说,如果在每一帧的循环代码中出现内存泄漏,很容易内存就会爆掉。
    C++11中可以使用shared_ptr来对指针进行引用计数,当引用为0时,自动释放指针指向的内存。但是shared_ptr为保证线程安全,运用互斥锁导致了一定性能损失,再加上使用shared_ptr的方式并不是很自然,因此Cocos没有用它来进行内存管理。

    相反,Cocos设计了一种基于引用计数的方法来更简便和自然地保证创建的UI元素不会发生内存泄漏,在这里予以介绍。
    在Cocos的UI中,所有的UI节点都是继承自Node类,Node又继承自Ref类,它负责UI节点的引用计数,类的声明如下:

    class CC_DLL Ref
    {
    public:
        void retain();
        void release();
        Ref* autorelease();
        unsigned int getReferenceCount() const;
    
    protected:
        Ref();
    
    public:
        virtual ~Ref();
    
    protected:
        /// count of references
        unsigned int _referenceCount;
        friend class AutoreleasePool;
    };
    

    其中_referenceCount中就记录了该对象的引用计数。retain和release分别负责增加和减少Ref对象的引用计数,代码如下:

    void Ref::retain()
    {
        CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
        ++_referenceCount;
    }
    
    void Ref::release()
    {
        CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
        --_referenceCount;
    
        if (_referenceCount == 0)
            delete this;
    }
    

    调用release时,在减掉引用计数后,如果引用计数已经是0,则会清空掉自己所指向的内存。
    当创建了一个Node节点时,此时它的引用计数为1,如果此时把它挂接在一个父节点上(比如addChild),则引用计数+1;如果将它从父节点remove掉,则引用计数-1。
    另外说一句,cocos实现了一套自己的常用数据结构,挂接父节点时增加引用计数的操作是在cocos自己实现的CCVector里做的,我当时也找了很久:)

    void pushBack(const Vector<T>& other)
    {
        for(const auto &obj : other) {
            _data.push_back(obj);
            obj->retain();
        }
    }
    

    这样虽然做了一定的封装,但是还不够智能,需要程序逻辑手动负责release。因此cocos还在Ref类中设计了autorelease函数:

    Ref* Ref::autorelease()
    {
        PoolManager::getInstance()->getCurrentPool()->addObject(this);
        return this;
    }
    

    该函数会将对象自己交由全局对象PoolManager进行管理,PoolManager将它放入当前的对象池AutoreleasePool中。在每一帧结束的时候执行release操作,将节点的引用计数减1,并清除引用计数为1的节点:

    void AutoreleasePool::clear()
    {
        std::vector<Ref*> releasings;
        //swap函数会交换两个vector的空间,因此执行完这一句后_managedObjectArray长度为0
        releasings.swap(_managedObjectArray);
        for (const auto &obj : releasings)
            obj->release();
    }
    

    那么应该在什么时候来执行autorelease呢?相信你已经猜到了,cocos在执行创建节点的create函数时,自动调用autorelease,使其纳入PoolManager的管理中:

    Node * Node::create()
    {
        Node * ret = new (std::nothrow) Node();
        if (ret && ret->init())
        {
            ret->autorelease();
        }
        else
        {
            CC_SAFE_DELETE(ret);
        }
        return ret;
    }
    

    总结一下:Cocos通过Ref类,来存储节点的引用计数,并通过retain和release来增减引用计数的值。在create创建节点时,会自动调用autorelease函数来将节点添加到AutoreleasePool中。在创建节点的这一帧结束时,AutoreleasePool会对这一帧创建的所有节点调用release函数,并清空队列。这一帧结束后,若该节点已经挂接到父亲上,则引用计数为1,继续保留;若该节点没有父亲,则引用计数为0,被回收。因为已挂接的节点引用计数已经是1,因此再将它们从父亲detach掉时,会再次调用release,引用计数清0,从而被回收。
    Cocos通过这种设计保证了在游戏运行时对内存的掌控。

  • 相关阅读:
    华为手机打不出logcat信息的解决办法
    android经典框架整理和学习
    电脑开机后的用户名跟密码全忘了,怎么办?
    分区修复软件使用简介
    Hardware Acceleration
    Ubuntu常用命令总结
    ubuntu 安装JDK方法
    Invalid layout of java.lang.String at value
    C# DateTime formate
    js获取url中的参数
  • 原文地址:https://www.cnblogs.com/wickedpriest/p/12238338.html
Copyright © 2011-2022 走看看