当前位置:   article > 正文

cocos2d-x 内存管理机制

cocos2d-x 内存管理机制

一、cc.Node

Node类是绝大部分类的父类,如Scene、LayerSprite,Button等,要理解cocos的内存管理,首先的是从Node类源码开始入手,抽丝剥茧,一步一步去挖掘

  1. Node * Node::create()
  2. {
  3. Node * ret = new (std::nothrow) Node();
  4. if (ret && ret->init())
  5. {
  6. ret->autorelease();
  7. }
  8. else
  9. {
  10. CC_SAFE_DELETE(ret);
  11. }
  12. return ret;
  13. }
  14. void Node::addChild(Node* child, int localZOrder, const std::string &name)
  15. {
  16. CCASSERT(child != nullptr, "Argument must be non-nil");
  17. CCASSERT(child->_parent == nullptr, "child already added. It can't be added again");
  18. addChildHelper(child, localZOrder, INVALID_TAG, name, false);
  19. }
  20. void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)
  21. {
  22. if (_children.empty())
  23. {
  24. this->childrenAlloc();
  25. }
  26. this->insertChild(child, localZOrder);
  27. ......
  28. }
  29. void Node::childrenAlloc()
  30. {
  31. _children.reserve(4);
  32. }
  33. // helper used by reorderChild & add
  34. void Node::insertChild(Node* child, int z)
  35. {
  36. _transformUpdated = true;
  37. _reorderChildDirty = true;
  38. _children.pushBack(child);
  39. child->_localZOrder = z;
  40. }
  41. void Node::removeFromParent()
  42. {
  43. this->removeFromParentAndCleanup(true);
  44. }
  45. void Node::removeFromParentAndCleanup(bool cleanup)
  46. {
  47. if (_parent != nullptr)
  48. {
  49. _parent->removeChild(this,cleanup);
  50. }
  51. }
  52. void Node::removeChild(Node* child, bool cleanup /* = true */)
  53. {
  54. // explicit nil handling
  55. if (_children.empty())
  56. {
  57. return;
  58. }
  59. ssize_t index = _children.getIndex(child);
  60. if( index != CC_INVALID_INDEX )
  61. this->detachChild( child, index, cleanup );
  62. }
  63. void Node::removeChild(Node* child, bool cleanup /* = true */)
  64. {
  65. // explicit nil handling
  66. if (_children.empty())
  67. {
  68. return;
  69. }
  70. ssize_t index = _children.getIndex(child);
  71. if( index != CC_INVALID_INDEX )
  72. this->detachChild( child, index, cleanup );
  73. }
  74. void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
  75. {
  76. // IMPORTANT:
  77. // -1st do onExit
  78. // -2nd cleanup
  79. if (_running)
  80. {
  81. child->onExitTransitionDidStart();
  82. child->onExit();
  83. }
  84. // If you don't do cleanup, the child's actions will not get removed and the
  85. // its scheduledSelectors_ dict will not get released!
  86. if (doCleanup)
  87. {
  88. child->cleanup();
  89. }
  90. // set parent nil at the end
  91. child->setParent(nullptr);
  92. _children.erase(childIndex);
  93. }

1、首先看create函数,这就函数是创建节点,这里需要注意是就是autorelease()这个方法,这个方法是干什么的呢?疑问1我们继续往下找,这个方法是Node父类的,顺着这条线,我们找到了Ref这个类,Ref是啥呢?这里先保留这个疑问2,等下在讲,现在继续看addChild()函数

2、再看addChild()->addChildHelper()->insertChild()->_children.pushBack(child)这个函数就是把节点pushBack到_children这个容器中去,然后设置name、tag,localZoOder,_children是什么呢?在Node中找,最后找到_children是一个Vector,装子节点的,每个Node上都有一个_children。这里可以看到pushBack这里调用了retain(),函数retain是啥呢?疑问3

3、最后看removeFromParent()->removeFromParentAndCleanup()->removeChild()->detachChild()->_children.erase(childIndex),这个函数就是把节点从_children中erase(),这里可以看到erase这里调用了release(),函数release()是啥呢?疑问4

  1. Vector<Node*> _children; ///< array of children nodes
  2. void pushBack(T object)
  3. {
  4. CCASSERT(object != nullptr, "The object should not be nullptr");
  5. _data.push_back( object );
  6. object->retain();
  7. }
  8. iterator erase(ssize_t index)
  9. {
  10. CCASSERT(!_data.empty() && index >=0 && index < size(), "Invalid index!");
  11. auto it = std::next( begin(), index );
  12. (*it)->release();
  13. return _data.erase(it);
  14. }

二、Ref

Ref到底是啥呢?还是从源码入手,我们找到Ref的源码,主要包括如下的函数:

  1. class CC_DLL Ref
  2. {
  3. public:
  4. void retain(); // ++_referenceCount
  5. void release(); // --_referenceCount
  6. Ref* autorelease(); // 添加到AutoReleasePool
  7. unsigned int getReferenceCount() const; // 获取引用次数
  8. protected:
  9. unsigned int _referenceCount; // 记录引用次数
  10. };
  11. NS_CC_END
  12. #endif // __BASE_CCREF_H__
  1. Ref::Ref()
  2. : _referenceCount(1) // 当创建Ref时,它的引用计数为1
  3. {
  4. #if CC_ENABLE_SCRIPT_BINDING
  5. static unsigned int uObjectCount = 0;
  6. _luaID = 0;
  7. _ID = ++uObjectCount;
  8. #endif
  9. #if CC_REF_LEAK_DETECTION
  10. trackRef(this);
  11. #endif
  12. }
  13. void Ref::retain()
  14. {
  15. CCASSERT(_referenceCount > 0, "reference count should greater than 0");
  16. ++_referenceCount;
  17. }
  18. void Ref::release()
  19. {
  20. CCASSERT(_referenceCount > 0, "reference count should greater than 0");
  21. --_referenceCount;
  22. if (_referenceCount == 0)
  23. {
  24. #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
  25. auto poolManager = PoolManager::getInstance();
  26. if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
  27. {
  28. CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
  29. }
  30. #endif
  31. #if CC_REF_LEAK_DETECTION
  32. untrackRef(this);
  33. #endif
  34. delete this;
  35. }
  36. }
  37. Ref* Ref::autorelease()
  38. {
  39. PoolManager::getInstance()->getCurrentPool()->addObject(this);
  40. return this;
  41. }

如何理解这个段代码呢?由上面代码可知道,cocos2d采用了引用计数的方法来进行来对对象进行  内存管理,(疑问2回答)Ref是cocos的内存管理类,cocos2d几乎所有的对象节点继承于Ref

1、首先看构造函数,当创建对象时,它的引用计数赋值为1(重点)

2、(疑问3回答)然后看retain函数,这个接口作用就是_referenceCount+1

3、(疑问4回答)接着看release函数,这个接口作用就是_referenceCount-1,当_referenceCount==0的时候,对象会被回收释放(重点)

4、再看autorelease()函数,这个函数有什么用呢?留疑1问接着往下看,这个函数里面出现了PoolManager这个单例,回到Ref.h,我们找到了AutoreleasePool这个类

friend class AutoreleasePool;

三、AutoreleasePool和PoolManager

  1. void AutoreleasePool::addObject(Ref* object)
  2. {
  3. _managedObjectArray.push_back(object);
  4. }
  5. void AutoreleasePool::clear()
  6. {
  7. #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
  8. _isClearing = true;
  9. #endif
  10. std::vector<Ref*> releasings;
  11. //将_managedObjectArray的数据和releasings交换,
  12. //以达到清理上一帧的_managedObjectArray这个vector,
  13. //来确保已经被release过的节点已经被踢出releasePool,具体请参考
  14. //c++ vector.swap原理
  15. releasings.swap(_managedObjectArray);
  16. // 遍历所有对象,进行引用计数-1,为0的销毁对象
  17. for (const auto &obj : releasings)
  18. {
  19. obj->release();
  20. }
  21. #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
  22. _isClearing = false;
  23. #endif
  24. }
  25. AutoreleasePool* PoolManager::getCurrentPool() const
  26. {
  27. return _releasePoolStack.back();
  28. }

AutoReleasePool,解释过来就是自动释放池,是来存放Node:create() 的一个容器。这里终于可以解释疑问1了,autorelease()就是把对象加入到了自动释放对象池中,这里只是添加进去了,哪里调用了AutoreleasePool::clear()函数呢?继续往下找,在CCDirector单例的manLoop()中找到了这个函数的调用:

  1. void DisplayLinkDirector::mainLoop()
  2. {
  3. if (_purgeDirectorInNextLoop)
  4. {
  5. _purgeDirectorInNextLoop = false;
  6. purgeDirector();
  7. }
  8. else if (! _invalid)
  9. {
  10. drawScene();
  11. // release the objects
  12. PoolManager::getInstance()->getCurrentPool()->clear();
  13. }
  14. }

coocs2d每一帧都会对当前AutoreleasePool进行clear(),把引用计数为0的对象清理。这里有几点需要解释一下

1、如果一个对象在创建后,在当前帧没有调用addChild(), 对象就会被销毁掉,所以对象一定要在当前帧结束前调用

2、对象在addChild()后_referenceCount由1变成2了(在Node和Ref有讲到过),然后通过AutoreleasePool的clear(),又从_referenceCount=2变为_referenceCount=1,clear()中使用swap()方法保证了节点对象在当前的AutoreleasePool中仅存在一帧的时间,避免重复遍历

3、调用removeFromParent来删除对象,引用计数由1变为0,对象会被释放

四、总结

cocos2dx的内存官方分为两种方式

1、手动方式:对象创建时_referenceCount赋值为1,调用retain(),_referenceCount+1,调用release(),_referenceCount-1,_referenceCount == 0时会被引擎回收掉释放内存

2、自动方式:在create()一个对象时,会调用autorelease()方法把该对象加入AutoreleasePool,在 mainLoop()方法中会调用当前AutoreleasePool中的clear()方法,clear()方法会遍历移除所有与该AutoreleasePool中的对象,并调用一次对象release()方法,如果这个对象_referenceCount == 0就会被引擎回收掉释放内存

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/233953?site
推荐阅读
相关标签
  

闽ICP备14008679号