码迷,mamicode.com
首页 > 其他好文 > 详细

CCObject的分析:release、retain 基于2.2.3,增加3.2 ref对比

时间:2014-10-06 19:13:10      阅读:326      评论:0      收藏:0      [点我收藏+]

标签:内存管理   cocos2d-x   2.2.3   3.2   ref   

CCSprite * fish = new CCSprite;
	CCLOG("After new: %d",fish->retainCount());
	fish->init();
	CCLOG("After init: %d",fish->retainCount());
	fish->retain();
	CCLOG("After retain: %d",fish->retainCount());
	fish->release();
	CCLOG("After release: %d",fish->retainCount());
	fish->autorelease();
	CCLOG("After autorelease: %d",fish->retainCount()); //实际操作了+1-1.


结果:

After new: 1
After init: 1
After retain: 2
After release: 1
After autorelease: 1
“HelloCpp.exe”(Win32): 已加载“C:\WINDOWS\SysWOW64\SogouTSF.ime”。无法查找或打开 PDB 文件。


class CC_DLL CCCopying
{
public:
    virtual CCObject* copyWithZone(CCZone* pZone);
};

/**
 * @js NA
 */
class CC_DLL CCObject : public CCCopying
{
public:
    // object id, CCScriptSupport need public m_uID
    unsigned int        m_uID;
    // Lua reference id
    int                 m_nLuaID;
protected:
    // count of references
    unsigned int        m_uReference;
    // count of autorelease
    unsigned int        m_uAutoReleaseCount;
public:
    CCObject(void);
    /**
     *  @lua NA
     */
    virtual ~CCObject(void);
    
    void release(void);
    void retain(void);
    CCObject* autorelease(void);
    CCObject* copy(void);
    bool isSingleReference(void) const;
    unsigned int retainCount(void) const;
    virtual bool isEqual(const CCObject* pObject);

    virtual void acceptVisitor(CCDataVisitor &visitor);

    virtual void update(float dt) {CC_UNUSED_PARAM(dt);};
    
    friend class CCAutoreleasePool;
};

如下:默认初始化reference为1,

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;
}

看看autorelease的源码就知道实现了+1和-1的操作:

CCObject* CCObject::autorelease(void)
{
    CCPoolManager::sharedPoolManager()->addObject(this);
    return this;
}
//<span style="font-family: Arial, Helvetica, sans-serif;">CCAutoreleasePool.cpp</span>
void CCPoolManager::addObject(CCObject* pObject)
{
    getCurReleasePool()->addObject(pObject);
}
void CCAutoreleasePool::addObject(CCObject* pObject)
{
    m_pManagedObjectArray->addObject(pObject); //赫然使用了CCArray。实现了+1的,随着3.2使用C++11语法后使用vector去管理就没有这么纠结了。


    CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");
    ++(pObject->m_uAutoReleaseCount); //同时在当前的pool中增加管理
    pObject->release(); // no ref count, in this case autorelease pool added.   //必须-1,否则泄露。
}



每个实现单例的类都应该提供清除操作:如下所示,以purge开头。

void CCPoolManager::purgePoolManager()
{
    CC_SAFE_DELETE(s_pPoolManager);
}


在2.2系列中,每个CCAutoreleasePool实际上就是一个CCArray,存储了一系列CCObject*,通过m_uAutoReleaseCount来计数。
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();
    }
}



回顾一下CCDiretor的初始化源码段:

    // scheduler
    m_pScheduler = new CCScheduler(); // 计时器
    // action manager
    m_pActionManager = new CCActionManager(); //动作管理器
    m_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false);
    // touchDispatcher
    m_pTouchDispatcher = new CCTouchDispatcher();  //触摸信号管理器
    m_pTouchDispatcher->init();

    // KeypadDispatcher
    m_pKeypadDispatcher = new CCKeypadDispatcher(); //键盘信号管理器

    // Accelerometer
    m_pAccelerometer = new CCAccelerometer(); // 加速器管理器

    // create autorelease pool
    CCPoolManager::sharedPoolManager()->push(); //创建一个当前的pool并加入PoolManager中,还是一个CCArray,名为:m_pReleasePoolStack,实际上触控更换为vector是从2.0的版本左右就计划好了。2.x版本是个过渡,也就是说,了解了2.x,更具版本更新说明,3.x不存在代沟。
通告层NotificationNode使用说明

下面有个NotificationNode,实际上和Scene属于UI种类,因为是后绘制,会遮盖当前scene,用途在于loading或者提示这种弹窗,记得触摸屏蔽的相关设置

// Draw the Scene
void CCDirector::drawScene(void)
{
    // calculate "global" dt
    calculateDeltaTime();

    //tick before glClear: issue #533
    if (! m_bPaused)
    {
        m_pScheduler->update(m_fDeltaTime);
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /* to avoid flickr, nextScene MUST be here: after tick and before draw.
     XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */
    if (m_pNextScene)
    {
        setNextScene();
    }

    kmGLPushMatrix();

    // draw the scene
    if (m_pRunningScene)
    {
        m_pRunningScene->visit();
    }

    // draw the notifications node
    if (m_pNotificationNode)
    {
        m_pNotificationNode->visit();
    }
    
    if (m_bDisplayStats)
    {
        showStats();
    }
    
    kmGLPopMatrix();

    m_uTotalFrames++;

    // swap buffers
    if (m_pobOpenGLView)
    {
        m_pobOpenGLView->swapBuffers();
    }
    
    if (m_bDisplayStats)
    {
        calculateMPF();
    }
}

原以为会是在drawscene内调用,谁知不是。这是个题外话,无关紧要的。


回归正题:

在经过CCDisplayLinkDirector继承CCDirector,在mainLoop中调用poolManager,这个才是真正的cocos程序循环,程序入口的是win32或者ios的主程序循环:

简化版:

int CCApplication::run()
{
    <span style="white-space:pre">// Initialize instance and cocos2d.
    if (!applicationDidFinishLaunching())
    {
        return 0;
    }


    CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
    pMainWnd->centerWindow();
    ShowWindow(pMainWnd->getHWnd(), SW_SHOW);


    while (1)
    {
        if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            // Get current time tick.
            QueryPerformanceCounter(&nNow);


            // If it's the time to draw next frame, draw it, else sleep a while.
            if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart)
            {
                nLast.QuadPart = nNow.QuadPart;
                CCDirector::sharedDirector()->mainLoop();
            }
            else
            {
                Sleep(0);
            }
            continue;
        }


        if (WM_QUIT == msg.message)
        {
            // Quit message loop.
            break;
        }


        // Deal with windows message.
        if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }</span>


    	
}


void CCDisplayLinkDirector::mainLoop(void)
{
    if (m_bPurgeDirecotorInNextLoop) /*虽然cocos2d允许多个director,准备兼容多窗口程序(实际上到目前3.2为止,cocos2d应用多为是单窗口,只有一个窗口句柄,支持多场景、层,支持C++11多线程的引擎 */
    {//导演类清理自己
        m_bPurgeDirecotorInNextLoop = false;
        purgeDirector();
    }
    else if (! m_bInvalid)
     {
         drawScene();
     
         // release the objects
         CCPoolManager::sharedPoolManager()->pop();        
     }
}


实际上终于回到了我们要密切关注的CCObject内存回收机制了:

每次pop()都去调用当前CCAutoreleasePool的clear操作,因为原理在于Director将游戏世界切片为每一帧,并为每一帧设定绘制时间长度,当GPU和CPU在设备上完成上一帧的制作,会计算一个时间长度,长度不够时会占用当前帧的可用时长,当前帧绘制的必要时长于剩余帧时长时跳过当前帧的CPU执行和GPU绘制,这就是为什么掉帧和碰撞穿透的原因,而在clear操作中,代码如下:

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();
    }
}

将当前帧的数据清理掉,m_uAutoReleaseCount变成0,但是仍未真正释放内存。这时候内存管理机制就依靠m_uReference来辨别是否需要清理。所有只使用addChild而不retain的都会随着父节点被清理掉。一方面我们可以自己pushPool,另一方面可以使用挂载在m_pobScenesStack中当前scene下。poolManager并不管理m_pobScenesStack主线的节点,只管理掉落在当前pool中通过autorelease增加至pool的CCObject。


3.2 ref对比待续

ref 内部变化:变得清爽多了,除了增加了 CC_USE_MEM_LEAK_DETECTION 预编译模式下的内存跟踪:(这两个不是ref内部函数)

static void trackRef(Ref* ref);
static void untrackRef(Ref* ref);

外,减少了原先CCObject中的各种引用,只剩下:_referenceCount,无论lua还是js、c++,公用一个引用计数,C++分支真的往库或者大型3D多线程发展啦。只要坚持了过渡期,学好python、Linux的相关编译,手游纯3D的可行性很高。

下面才是新增的内存追踪显示函数:

#if CC_USE_MEM_LEAK_DETECTION
public:
    static void printLeaks();
#endif

新的AutoreleasePool已经使用:std::vector<Ref*> _managedObjectArray; 标准C++作为容器,通过ref.autorelease()调用的代码中不在进行+-1啦,

void AutoreleasePool::addObject(Ref* object)
{
    _managedObjectArray.push_back(object);
}

同时在mainLopp中变成了直接调用Autorelease的clear()操作:

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (! _invalid)
    {
        drawScene();
     
        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}


3.2中

void Director::drawScene()
{
    // calculate "global" dt
    calculateDeltaTime();
    
    // skip one flame when _deltaTime equal to zero.
    if(_deltaTime < FLT_EPSILON)
    {
        return;
    }

    if (_openGLView)
    {
        _openGLView->pollInputEvents();
    }

    //tick before glClear: issue #533
    if (! _paused)
    {
        _scheduler->update(_deltaTime);
        _eventDispatcher->dispatchEvent(_eventAfterUpdate);
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /* to avoid flickr, nextScene MUST be here: after tick and before draw.
     XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */
    if (_nextScene)
    {
        setNextScene();
    }

    pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    // draw the scene
    if (_runningScene)
    {
        _runningScene->visit(_renderer, Mat4::IDENTITY, false);
        _eventDispatcher->dispatchEvent(_eventAfterVisit);
    }

    // draw the notifications node
    if (_notificationNode)
    {
        _notificationNode->visit(_renderer, Mat4::IDENTITY, false);
    }

    if (_displayStats)
    {
        showStats();
    }

    _renderer->render();
    _eventDispatcher->dispatchEvent(_eventAfterDraw);

    popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    _totalFrames++;

    // swap buffers
    if (_openGLView)
    {
        _openGLView->swapBuffers();
    }

    if (_displayStats)
    {
        calculateMPF();
    }
}
通过调用setNextScene();来在每次绘制之前增加scene的引用计数,这样子在drawscene之后调用clear就能清楚了无用的内存ref对象:

void Director::setNextScene()
{
    bool runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene) != nullptr;
    bool newIsTransition = dynamic_cast<TransitionScene*>(_nextScene) != nullptr;

    // If it is not a transition, call onExit/cleanup
     if (! newIsTransition)
     {
         if (_runningScene)
         {
             _runningScene->onExitTransitionDidStart();
             _runningScene->onExit();
         }
 
         // issue #709. the root node (scene) should receive the cleanup message too
         // otherwise it might be leaked.
         if (_sendCleanupToScene && _runningScene)
         {
             _runningScene->cleanup();
         }
     }

    if (_runningScene)
    {
        _runningScene->release();
    }
    _runningScene = _nextScene;
    _nextScene->retain();
    _nextScene = nullptr;

    if ((! runningIsTransition) && _runningScene)
    {
        _runningScene->onEnter();
        _runningScene->onEnterTransitionDidFinish();
    }
}

同时看一下Node::addChild的操作,能解决我们的疑惑,使用了addChild和Autorelease两种内存管理的区别:

void Node::addChild(Node* child, int localZOrder, const std::string &name)
{
    CCASSERT(child != nullptr, "Argument must be non-nil");
    CCASSERT(child->_parent == nullptr, "child already added. It can't be added again");
    
    addChildHelper(child, localZOrder, INVALID_TAG, name, false);
}
void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)
{
    if (_children.empty())
    {
        this->childrenAlloc();
    }
    
    this->insertChild(child, localZOrder);
    
    if (setTag)
        child->setTag(tag);
    else
        child->setName(name);
    
    child->setParent(this);
    child->setOrderOfArrival(s_globalOrderOfArrival++);
    
#if CC_USE_PHYSICS
    // Recursive add children with which have physics body.
    Scene* scene = this->getScene();
    if (scene != nullptr && scene->getPhysicsWorld() != nullptr)
    {
        child->updatePhysicsBodyTransform(scene);
        scene->addChildToPhysicsWorld(child);
    }
#endif
    
    if( _running )
    {
        child->onEnter();
        // prevent onEnterTransitionDidFinish to be called twice when a node is added in onEnter
        if (_isTransitionFinished) {
            child->onEnterTransitionDidFinish();
        }
    }
    
    if (_cascadeColorEnabled)
    {
        updateCascadeColor();
    }
    
    if (_cascadeOpacityEnabled)
    {
        updateCascadeOpacity();
    }
}
这里要注意了,addChild中用到的vector不是std的vector,是CCVector,cocos2d重写的,

    void pushBack(T object)
    {
        CCASSERT(object != nullptr, "The object should not be nullptr");
        _data.push_back( object );
        object->retain();
    }


所以实际上我们看似使用AddChild交给cocos2d的渲染树帮助我们管理内存和启用AutoreleasePool形式管理内存是不相干,实际就相当于在以pool为载体的区域内建立了一批对象,根据需要去retian,或者挂载了渲染树上(每棵渲染树就是以scene为起点,addChild执行一次retian),每次渲染结束后都会清理一次内存池。所有在new之后,不调用Autorelease就要我们手动采取C++标准方式去管理内存;如果启用Autorelease的机制,因为调用Autorelease实在drawscene(cpu游戏主线和GPU渲染)之后,没有了之前存在可能申请了马上被清理的可能,所以不需要+-1,只需要在clear的时候判断是否为0。AddChild的区别在于挂载在渲染树上的节点_referenceCount初始为2,在执行一次clear之后就不在属于PoolManager的当中的AutoReleasePool,而是只属于渲染树啦。

渲染树的清理:

void Director::replaceScene(Scene *scene)
{
    CCASSERT(_runningScene, "Use runWithScene: instead to start the director");
    CCASSERT(scene != nullptr, "the scene should not be null");
    
    if (scene == _nextScene)
        return;
    
    if (_nextScene)
    {
        if (_nextScene->isRunning())
        {
            _nextScene->onExit();
        }
        _nextScene->cleanup();
        _nextScene = nullptr;
    }

    ssize_t index = _scenesStack.size();

    _sendCleanupToScene = true;
    _scenesStack.replace(index - 1, scene);

    _nextScene = scene;
}

 _nextScene->cleanup();
调用的是Node的cleanup,递归形式完成渲染树的清理。


提示: //利用静态函数实现每个pool创建自动添加至Manager,用的真妙!!C++只会越用越熟啊。

PoolManager* PoolManager::getInstance()
{
    if (s_singleInstance == nullptr)
    {
        s_singleInstance = new PoolManager();
        // Add the first auto release pool 
        new AutoreleasePool("cocos2d autorelease pool");
    }
    return s_singleInstance;
}
AutoreleasePool::AutoreleasePool(const std::string &name)
: _name(name)
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
, _isClearing(false)
#endif
{
    _managedObjectArray.reserve(150);
    PoolManager::getInstance()->push(this);
}


所以每次执行完clear,Manager中除了渲染树和_referenceCount>1都会被清理掉;可是pool被清理之后_referenceCount>=1的内存就如渲染树般泄露了,这个时候就需要我用自己release(delete)了。为什么会出现这样子的情况呢,实际上源于早前兼容ObjectC的代码继承过来的管理机制,可能之后Pool和Autorelease的机制会被清理调用,只保存渲染树和手动retian、release。这和之后启用C++11 shared_ptr<typename>有关。因为3.2的pool实际上可有可无啦,完全可以让程序去实现了内存管理。

至于ref继承了clone接口,这个人尽皆知,就不分析啦。可惜目前我仍然无法搭建git环境,我最怕搭建环境了,比如花了4天去配置cocos2dx 2.x 和quick的win安卓环境,后来放弃了之后发现3.x完全是方便至极。虚拟机mac环境早搞定了,只剩sdk下载。 


下面是源码推荐的用法:

// Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.
            // This happens when 'autorelease/release' were not used in pairs with 'new/retain'.
            //
            // Wrong usage (1):
            //
            // auto obj = Node::create();   // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool.
            // obj->autorelease();   // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first.
            //
            // Wrong usage (2):
            //
            // auto obj = Node::create();
            // obj->release();   // Wrong: obj is an autorelease Ref, it will be released when clearing current pool.
            //
            // Correct usage (1):
            //
            // auto obj = Node::create();
            //                     |-   new Node();     // `new` is the pair of the `autorelease` of next line
            //                     |-   autorelease();  // The pair of `new Node`.
            //
            // obj->retain();
            // obj->autorelease();  // This `autorelease` is the pair of `retain` of previous line.
            //
            // Correct usage (2):
            //
            // auto obj = Node::create();
            // obj->retain();
            // obj->release();   // This `release` is the pair of `retain` of previous line.


CCObject的分析:release、retain 基于2.2.3,增加3.2 ref对比

标签:内存管理   cocos2d-x   2.2.3   3.2   ref   

原文地址:http://blog.csdn.net/jingzhewangzi/article/details/39810275

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!