cocos2d-x 源码剖析(7)

上节讲了一下CCNode的绘制链,但是其中遗漏了一个重要的点。还记得我说的那个调用层次很深的那个函数吗?
void CCNode::insertChild(CCNode* child, int z)
{
    m_bReorderChildDirty = true;
    ccArrayAppendObjectWithResize(m_pChildren->data, child);
    child->_setZOrder(z);
}

这个函数本身不是很重要,重要的是是m_pChildren,它申明如下:

CCArray *m_pChildren;

ccArray有一个特性,它会将加入其中的元素的retina count加1。(我已经忍受不了cocos2d-x的缩进啦…)

void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)
{
  ccArrayEnsureExtraCapacity(arr, 1);
  ccArrayAppendObject(arr, object);
}

/** Appends an object. Behavior undefined if array
 doesn't have enough capacity. */
void ccArrayAppendObject(ccArray *arr, CCObject* object)
{
  CCAssert(object != NULL, "Invalid parameter!");
  object->retain();
  arr->arr[arr->num] = object;
  arr->num++;
}

加1的便是那个retain调用。一个C++的引擎底层,居然大量利用C风格的API,这令我有些诧异。而且最近写代码,突然意识到这种API出乎意料的简洁和灵活。不经有些感触。突然又对ORX有了兴趣,想看看到底是如何觉得这个C引擎难以使用的。

retain count即引用计数,是cocos2d-x的一大核心。如果是C++转cocos2d-x程序,一定要好好掌握这一点。它能让你从new 和 delete中解脱出来,也能让你更加便捷的组织代码。前提是你要了解它,小心的呵护它,不要瞒着它做一些不好的事。如果你正常的调用cocos2d-x的api,你是无需关心retina count,它被维护的很好。如果你想保存一份本地的指针,注意retain,并在不需要的时候调用release即可。忘记C++吧,既然选择了cocos2d-x,你就当是在开发objective-c好了。话是这么说,但是我们还是要了解一些它的细节,算是越轨之后的自赎。CCNode从CCObject继承了retain count:

void CCObject::release(void)
{
    CCAssert(m_uReference > 0, "reference count should greater than 0");
    --m_uReference;
 
    if (m_uReference == 0)
    {
        delete this;
    }
}
 
void CCObject::retain(void)
{
    CCAssert(m_uReference > 0, "reference count should greater than 0");
 
    ++m_uReference;
}

CCObject::CCObject(void)
: m_nLuaID(0)
, m_uReference(1) // when the object is created, 
                  //the reference count of it is 1
, m_uAutoReleaseCount(0)
{
    static unsigned int uObjectCount = 0;
 
    m_uID = ++uObjectCount;
}

要注意的是构造函数中引用计数被设置为1,而且在release中,如果变成了0便会立马调用delete 函数结束自己。如果你把自己retian的那份计数release掉了,你就不应该再操作这个对象了,哪怕这个对象本身的引用计数没有设置为0。我们再看看create函数:

CCNode * CCNode::create(void)
{
    CCNode * pRet = new CCNode();
    if (pRet && pRet->init())
    {
        pRet->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(pRet);
    }
    return pRet;
}

我们知道new出来后,构造函数被调用,这时的引用计数为1。这时调用了autorelease函数:

CCObject* CCObject::autorelease(void)
{
    CCPoolManager::sharedPoolManager()->addObject(this);
    return this;
}

你会发现,这个autorelease函数本身没有做任何处理。只是将它添加到了自动池中而已。如果你create出来之后,调用retinacount,你会发现它还是为1.如果我们调用到addChild将创建出来的Node添加到父节点中,你会发现它的retain count变成了2。这是不科学的,应为只有它的Parent保存了它的一份引用而已。如果你在下一帧中,再想办法调用它的retina count,它又就会变成1,这时候才是正常的。其中的奥妙就在于那个autorelease函数。cocos2d-x的自动池在下一帧中将里面的元素都调用一遍release,然后移除这个元素。也就是说自动池再也不管理它了。

追到细节中去:

void CCPoolManager::addObject(CCObject* pObject)
{
    getCurReleasePool()->addObject(pObject);
}


void CCAutoreleasePool::addObject(CCObject* pObject)
{
    m_pManagedObjectArray->addObject(pObject);
 
    CCAssert(pObject->m_uReference > 1, "reference count should 
                              be greater than 1");
    ++(pObject->m_uAutoReleaseCount);
    pObject->release(); // no ref count, in this case
                        // autorelease pool added.
}

我们知道, m_pManagedObjectArray->addObject(pObject);会将计数加1,为了维护正确,所以必须调用release将计数减1.而且增加了auto_release的次数。那么自动池是如何在下一帧中将计数减1的呢。其实我们早就看到这个调用了——那就是主循环啊:

void CCDisplayLinkDirector::mainLoop(void)
{
    if (m_bPurgeDirecotorInNextLoop)
    {
        m_bPurgeDirecotorInNextLoop = false;
        purgeDirector();
    }
    else if (! m_bInvalid)
     {
         drawScene();
 
         // release the objects
         CCPoolManager::sharedPoolManager()->pop();        
     }
}

那个CCPoolManager::sharedPoolManager()->pop();便是:

void CCPoolManager::pop()
{
  if (! m_pCurReleasePool)
  {
    return;
  }
 
  int nCount = m_pReleasePoolStack->count();
 
  m_pCurReleasePool->clear();
 
  if(nCount > 1)
  {
    m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
 
//         if(nCount > 1)
//         {
//             m_pCurReleasePool = m_pReleasePoolStack->
//               objectAtIndex(nCount - 2);
//             return;
//         }
    m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->
      objectAtIndex(nCount - 2);
  }
 
    /*m_pCurReleasePool = NULL;*/
}

起移除作用的是下面这个函数:

void CCAutoreleasePool::clear()
{
    if(m_pManagedObjectArray->count() > 0)
    {
        //CCAutoreleasePool* pReleasePool;
#ifdef _DEBUG
        int nIndex = m_pManagedObjectArray->count() - 1;
#endif
 
        CCObject* pObj = NULL;
        CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)
        {
            if(!pObj)
                break;
 
            --(pObj->m_uAutoReleaseCount);
            //(*it)->release();
            //delete (*it);
#ifdef _DEBUG
            nIndex--;
#endif
        }
 
        m_pManagedObjectArray->removeAllObjects();
    }
}

这时候将auto release次数减1,并将当前的自动池清空。正如添加的时候元素计数加1,移除的时候计数也会被减1.实际的代码我就不贴了。

有点出乎意料的是CCPoolManager管理的居然是一个内存池堆。而且是一帧释放掉一个,我想这可能是出于性能上考虑。毕竟如果全部在下一帧中处理的话,有可能出现卡帧的现象。我没有什么objective-c的经验,我想这应该是借鉴的objective-c的。当要处理大量auto release对象时,记得有这个函数。虽说这样做性能其实不高。还有一点值得一体,如果你不手动调用push的话,默认只有一个自动池,它是在sharedPoolManager()这个函数调用时利用push产生的。这种手法见多不怪了。

大家可以写个小例子来追踪引用计数的变化,这对于理解cocos2d-x是有相当大的帮助的。而且你会发现,cocos2d-x本身把引用维护得非常值得让你探索。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值