-
-
Notifications
You must be signed in to change notification settings - Fork 205
Memory Management
Axmol is based on Cocos2d-x, and Cocos2d-x is based on Cocos2d-iphone, which was implemented in Objective-C.
The (old) memory management policy of Objective-C can be read about here
When cocos2d-iphone was ported over to C++ and became Cocos2d-x, the same memory management model was used. Given Axmol is based on Cocso2d-x v4, it has also inherited that same memory management model.
The majority of classes in Axmol inherit from ax::Ref
, which handles the reference counting for the instances created from those classes. Reference counting is a way to track how many explicit links there are to a specific resource, which in this case is an object of a class based on ax::Ref
(such as Node, Sprite etc. etc.).
If you create an object with the new
operator, then that object will instantly have a reference count of 1. You can see this in the constructor of Ref
(see Ref.cpp
):
Ref::Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
...
{
...
}
If an object is explicitly freed via delete
, then memory associated with it will be freed, but if areas of the code hold references to this object, then this will result in undefined behaviour (crashes etc.). The reason is that there is no way to notify the holders of those references that the memory associated with that object is no longer valid. So, how do we avoid this situation?
The solution is to use the Ref::retain()
/Ref::release()
methods.
Calling retain()
on an object will increase its reference count by 1.
Calling release()
on an object will decrease its reference count by 1. If at any point the reference count becomes 0, the object automatically frees itself, via the delete
operator. Refer to the code in void Ref::release()
(see Ref.cpp
):
void Ref::release()
{
AXASSERT(_referenceCount > 0, "reference count should be greater than 0");
--_referenceCount;
if (_referenceCount == 0)
{
...
delete this;
}
}
One very important rule to follow is this: A retain()
must always be matched with a release()
, otherwise this will result in memory leaks, where objects are not freed from memory, yet no other object holds a reference to them.
So, if you explicitly call retain()
on an object, then you must, at some point, also call release()
on that same object (when you no longer require it).
You should not explicitly free an object (which inherits from Ref
) via delete
; if the object is no longer required, then simply call release()
on it. If no other objects are holding references to it, meaning reference count becomes 0, then that object will free itself.
As an example, you have an object of type Scene
, and you need to hold a reference to a sprite in this scene:
class MyScene : public ax::Scene
{
public:
private:
ax::Sprite* _mySprite; // This is the reference to the sprite
}
Let's say the sprite is created like this (this isn't how you typically create a new instance of a sprite, where you should instead use a create
method, but that's covered later):
_mySprite = new Sprite(); // _referenceCount = 1
this->addChild(_mySprite); // _referenceCount = 2, since `addChild` explicitly calls `retain()` on the object
In this case, a retain
/release
combination is not required, since the number of objects holding a reference to our sprite is the same as the reference count. One reference held in MyScene
in _mySprite
, and another held by the adding that sprite to the child nodes of that scene. All up that equals 2.
If at any point you no longer require the explicit reference to _mySprite
, then simply release it, and set that member variable to nullptr
to indicate it is no longer valid.
_mySprite->releasee(); _referenceCount becomes 1
_mySprite = nullptr;
At this point the sprite still exists, and has not been freed, because the refernece count is 1. That referene is still being held within the scene child node list. When that scene itself is freed (also by a release
) and its reference count becomes 0, so the destructor is called, then the scene calls release
on all child nodes belonging to it. Any child objects that are released and now have a reference count of 0, such as our eaxmple sprite, will automatically be deleted from memory. So far so good... or not quite!
Let's say we do this:
Sprite* sprite = new Sprite(); // _referenceCount = 1
this->addChild(sprite); // _referenceCount = 2, since `addChild` explicitly calls `retain()` on the object
We now have a problem. As noted earlier, when the destructor of the scene is called, release()
is called on all child nodes of the scene. So, in this case, our example sprite has a reference count of 2, so when release
is called, it deducts 1 from the count, so 2 - 1 = 1
; the final reference count is 1. The sprite does not delete itself from memory, since its reference count is not yet 0. Nothing else holds a reference to that sprite, so now we've lost all links to that sprite and the memory associated with it. We have no way to delete it from memory, so we've just lost that bit of memory. This is the start of a very nasty memory leak; the more objects we create via new
, and don't explicitly delete them, the more memory we lose, until eventually we run out of memory.
So, what is the solution to this? Do we need to hold references and call delete
on each and every object we create via new
? .... do we really want to add so much more code thatrequired to track each and every object of type Ref
, and then figure out if that object reference count is equal to 1 in order to explicitly call delete
... that's just nasty. So, no, there is another bit to this puzzle, a method called autorelease()
(in Ref.cpp
).
The purpose of this call is to add an object (of type Ref
) to a list, and at the end of the current main loop cycle, release()
is explicitly called on each and every item in that list. This only happens once, since the after these objects are released, the list is cleared, so they no longer exist in that list.
Why would we want to do this? The best way is to show you an example, so let us use the same sprite code as earlier:
Sprite* sprite = new Sprite(); // _referenceCount = 1
sprite->autorelease(); // adds this object to the auto-release pool (the list of objects to be released on the next cycle of the main loop)
this->addChild(sprite); // _referenceCount = 2, since `addChild` explicitly calls `retain()` on the object
As you can see, all we have done is added the call to sprite->autorelease();
after we created a new instance of the sprite via new
. So, what does this actually do?
Let's think of this in main loop cycles:
- Current main loop cycle, we create the sprite and add it to our scene. At this point, the reference count of the sprite is equal to 2 (as you can see above)
- At the end of the current main loop, just prior to starting the next loop, the auto-release pool is iterated through, and
release()
is called on every object in that list. Since this list contains a reference to our sprite (currently at_referenceCount = 2
), oncerelease()
is called on it, the reference count becomes 1. The auto-release pool is then emptied, so it no longer holds a reference to our sprite.
So, now what?
We know the sprite now has a reference count of 1. As we mentioned earlier, if the parent scene of this sprite is freed from memory (the destructor of the scene is called), then release()
will be called on every child node in that scene. Since our sprite is at a reference count of 1, when release
is called on it, that reference count becomes 0, and we know that when it is 0, the sprite will free itself automatically. The memory associated with the sprite is freed correctly, so no more memory leak!
So, what do know this far:
- If an object of type
Ref
is created via thenew
operator, then it must be freed by a call todelete
. We do not want to manage this manually, so... - In order to avoid managing the lifetime of objects manually, we add newly created objects to the auto-release pool via a call to
autorelease()
, to automatically callrelease
on objects at the end of our current main loop cycle . This will ensure that the object is freed correctly if nothing is holding a reference to it (so reference count is 0).
By convention, you would typically see static methods named create****
in all classes inheriting from Ref
. These are convenience methods that create a new instance of that class, and automatically call autorelease()
on that new instance, reducing the amount of code we need to write, and saving us from being forgetting to call autorelease()
ourselves; basically, less boilerplate and error-prone code.
Now, just so you are aware, in certain cases we may not want to call autorelease()
on objects, but that's because we know that we only need a single reference to that object, and we are well aware of needing to free that object when it is no longer required. Only do this if you know what you're doing, and if you have a specific reason to be doing this.
NOTE: If for some reason you need to call autorelease
more than once in the SAME main-loop cycle (basically, never, perhaps a programming mistake), then the rule is that there must be 1 reference for each call to autorelease()
. If you call autorelease()
3 times on an object, then that object must have a reference count of 3 or more.
For example:
We know that a new
on an object of type Ref
always has an initial reference count of 1. We can call autorelease()
on this.
If we want to call autorelease()
again, then we would need to call retain()
to increase the reference count by 1 as well.
Sprite* sprite = new Sprite(); // _referenceCount = 1
sprite->autorelease(); // will delete 1 reference at the end of the main loop
sprite->retain(); // _referenceCount = 2
sprite->autorelease(); // will delete 1 reference at the end of the main loop
sprite->retain(); // _referenceCount = 3
sprite->autorelease(); // will delete 1 reference at the end of the main loop
So, when it gets to the end of the main loop, this happens (pseudocode):
foreach (item in list)
{
item->release(); // _referenceCount = _referenceCount - 1
}
Since our example sprite is in the list 3 times, then release()
is called 3 times on it, so the end result is that it equals 0, so it will be freed, since we are not using it anywhere else (nothing else holds a reference to it).
Once again, there should be no reason at all to add it to the auto-release pool more than once within the same main loop, and if that does happen, then it is most likely a coding error.
In summary:
- Use the static
create
methods to create new instances of classes inheriting fromRef
. In your own sub-classes of AxmolRef
objects (Node, Sprite etc), ensure that you implement thesecreate
methods too. The create method should (almost always) have a call toautorelease()
on that new object. - All sub-classes of Node will automatically call
retain()
andrelease()
on child nodes (added viaaddChild()
etc.). We do not need to explicitly retain or release these objects. - If we need to hold a reference to an Axmol object for some particular reason (reference to previous example with a field named
_mySprite
), then we must callretain()
when we store the reference, andrelease()
when we no longer require it. Example:
class MyNode : public ax::Node
{
public:
~MyNode(); // destructor
bool init() override;
private:
ax::Sprite* _mySprite = nullptr;
}
Implementation of MyNode
:
// In current main loop cycle init() is called:
bool MyNode::init() {
...
_mySprite = Sprite::create(); // _referenceCount = 1, added to auto-release pool
_mySprite->retain(); // _referenceCount = 2 (in auto-release pool)
addChild(_mySprite); // _referenceCount = 3 (in auto-release pool)
...
}
// Destructor
MyNode::~MyNode()
{
if (_mySprite != nullptr)
_mySprite->release(); // _referenceCount = _referenceCount - 1
}
On the next main loop cycle, the auto-release pool is processed, so 1 is deducted from all objects in that pool. After this, _mySprite
will now have a reference count of 2, which is correct, since it's added as a child of MyNode
, and we also hold an explicit reference to it in _mySprite
; a total of 2 references. These match with the 2 release()
calls that will occur, one if that child is removed from the list of children in MyNode
(such as MyNode::removeAllChildren()
etc.), and another when we explicitly call _mySprite->release()
if the MyNode
destructor is called.
To reduce the amount of code used to retain, release and set a variable to null, there are a few macros to use:
AX_SAFE_RELEASE(object)
= If object is not null, then this calls object->release()
AX_SAFE_RELEASE_NULL(object)
= If object is not null, then this calls object->release()
and object = nullptr
AX_SAFE_RETAIN(object)
= If object is not null, then this calls object->retain()
More macros can be found in PlatformMacros.h
.