cocos2d-x - Ref 引用计数

Ref

Ref是cocos2d里面所有类型的基类,用于引用计数。

  • Ref::Ref():构造函数的初始化列表会将该对象的引用计数赋值为1(Ref::Ref(): _referenceCount(1))。

  • void Ref::retain():将引用计数+1。

  • void Ref::release():将引用计数-1。如果引用计数为0则删除该对象(delete this)。#if代码块的意思是为了确保如果当前对象在自动释放池中(poolManager->isObjectInPools(this)),那么只有在池子在进行清理时(poolManager->getCurrentPool()->isClearing())才能被释放,否则就断言不通过。

    void Ref::release()
    {
        --_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
            delete this;
        }
    }
    
  • Ref* Ref::autorelease():将对象添加到自动释放池中。

  • unsigned int Ref::getReferenceCount() const:获取引用计数。

  • CCPlatformMacros.h头文件中有一些关于指针相关操作的宏定义:

    #define CC_SAFE_DELETE(p)           do { delete (p); (p) = nullptr; } while(0)
    #define CC_SAFE_DELETE_ARRAY(p)     do { if(p) { delete[] (p); (p) = nullptr; } } while(0)
    #define CC_SAFE_FREE(p)             do { if(p) { free(p); (p) = nullptr; } } while(0)
    #define CC_SAFE_RELEASE(p)          do { if(p) { (p)->release(); } } while(0)
    #define CC_SAFE_RELEASE_NULL(p)     do { if(p) { (p)->release(); (p) = nullptr; } } while(0)
    #define CC_SAFE_RETAIN(p)           do { if(p) { (p)->retain(); } } while(0)
    
  • 引用计数管理
    • 有开始就有结束。对于newcopy创建的对象,一旦不再使用,要调用release()来释放。
    • 有引用就有释放。被引用时调用retain()增加引用计数,不再被引用时也要调用release()减少引用计数。
    • 参数传递更替引用。当对象指针作为参数传递时,如果在函数内被其他对象所引用,则需要先释放旧的引用对象,在增加新的对象引用。e.g.
      void NodeGrid::setGrid(GridBase *grid)
      {
          CC_SAFE_RELEASE(_nodeGrid);
          CC_SAFE_RETAIN(grid);
          _nodeGrid = grid;
      }
      
      还有一个更为常见的例子就是NodeaddChildremoveChild方法在实现的过程中已经进行了相应的retainrelease操作。从addChild的函数调用栈可以看到最终在CCVector::pushBack进行了retain操作;removeChild也是类似的过程。
      void Node::addChild(Node *child, int zOrder) ->
      void Node::addChild(Node* child, int localZOrder, const std::string &name) ->
      void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag) ->
      void Node::insertChild(Node* child, int z) ->
      void CCVector::pushBack(T object)
      {
          CCASSERT(object != nullptr, "The object should not be nullptr");
          _data.push_back( object );
          object->retain();
      }
      
  • 自动释放池
    • 调用autoRelease方法会将当前对象加入到自动释放池AutoreleasePool中。
      Ref* Ref::autorelease()
      {
          PoolManager::getInstance()->getCurrentPool()->addObject(this);
          return this;
      }
      
      一般而言,各类型在使用静态的create方法创建实例时就会调用其autoRelease方法。e.g.
      Node * Node::create()
      {
          Node * ret = new (std::nothrow) Node();
          if (ret && ret->init())
          {
              ret->autorelease();
          }
          else
          {
              CC_SAFE_DELETE(ret);
          }
          return ret;
      }
      
    • 池管理器PoolManager使用一个向量来维护所有的自动释放池。
      AutoreleasePool* PoolManager::getCurrentPool() const
      {
          return _releasePoolStack.back();
      }
      
      void PoolManager::push(AutoreleasePool *pool)
      {
          _releasePoolStack.push_back(pool);
      }
      
      void PoolManager::pop()
      {
          CC_ASSERT(!_releasePoolStack.empty());
          _releasePoolStack.pop_back();
      }
      
    • 自动释放池AutoreleasePool也是使用向量_managedObjectArray来维护指针对象。在clear方法中会将向量_managedObjectArray的所有元素交换出来存到临时向量releasings中(实际上就是想把原向量清空),然后遍历releasings的所有元素都执行release操作,即引用计数减一,如果元素的引用计数减到0就会在其relese方法中被释放掉。
      void AutoreleasePool::addObject(Ref* object)
      {
          _managedObjectArray.push_back(object);
      }
      
      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
      }
      
    • Director会在每一帧的最后调用自动释放池的clear方法。
      void Director::mainLoop()
      {
          drawScene();
       
          // release the objects
          PoolManager::getInstance()->getCurrentPool()->clear();
      }
      

    总的来说,自动释放池做的工作就是保证在当前帧创建的节点,如果它在这一帧中都没有被引用(被添加为子节点等操作),那么在这一帧最后就会被释放掉。假设当前帧新创建(create)的节点在当前帧中被addChild了,那么它的引用计数变成2(构造函数会将引用计数设为1,并且create的时候会加入到自动释放池的向量中),那么在这一帧的最后调用自动释放池会将从向量中取出这个节点(向量会被清空),并且调用节点的release方法,这时候节点的引用计数变成1不会被释放掉。在以后的帧中,自动释放池的向量都不会再持有该节点(除非是主动调用autorelease方法又把节点加到自动释放池的向量中),所以这个节点就一直存活在场景中,直到它被removeChild时才会调用release方法,引用计数减为0从而被释放掉。

【参考资料】
[1] Cocos2D-X游戏开发技术精解 - 第10章 内存管理机制
[2] do…while(0)的妙用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值