-
-
Notifications
You must be signed in to change notification settings - Fork 204
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->release(); _referenceCount becomes 1
_mySprite = nullptr;
At this point the sprite still exists, and has not been freed, because the reference count is 1. That reference 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 example 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 that required 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 (pseudo-code):
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 a
Ref
object for some particular reason, then we must callretain()
when we store the reference, andrelease()
when we no longer require it. For example:
class MyNode : public ax::Node
{
public:
~MyNode(); // destructor
bool init() override;
private:
ax::Sprite* _mySprite = nullptr;
}
Implementation of MyNode
:
bool MyNode::init() // In current main loop cycle init() is called:
{
//...
_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)
//...
}
MyNode::~MyNode() // Destructor
{
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 callsobject->release()
-
AX_SAFE_RELEASE_NULL(object)
= If object is not null, then this callsobject->release()
and setsobject = nullptr
-
AX_SAFE_RETAIN(object)
= If object is not null, then this callsobject->retain()
More macros can be found in PlatformMacros.h
.
Diagnostic functionality exists that you can enable during development to help with detecting memory leaks and dangling references of Ref
objects. This comes in the form of the AX_REF_LEAK_DETECTION
preprocessor define (see Ref.h
):
#define AX_REF_LEAK_DETECTION 0
When AX_REF_LEAK_DETECTION
is set to 1, all Axmol object references will be tracked, which gives us the ability to check for a few different issues.
The reference count is tracked, so at any point, you may call the static method Ref::printLeaks()
to print out a list of all objects remaining in the tracking list. An object would only be in the list if its reference count is greater than 0, meaning it has not yet been freed. The Ref::printLeaks()
method can be called at any time, but that wouldn't be really useful. Where you want to call this is as close to the last object to be destroyed when closing the application. For example, from AppDelegate::~AppDelegate()
:
AppDelegate::~AppDelegate()
{
#if AX_REF_LEAK_DETECTION
ax::Ref::printLeaks();
#endif
}
On application exit, you will get a list of objects that have not yet been freed. Generally, this would mean that release()
was never called on those objects. The higher the reference count shown for an object, the more areas of code holding a reference to it.
When memory is being freed for a Ref
object, the reference count is checked to ensure it is equal to 0, since nothing should be holding onto a reference to this object (for example, a pointer to it).
If the reference count is not equal to zero, then something is still be holding a reference to this object. What this means is that when the memory is freed, any reference to that object will now no longer be pointing to valid content, and any attempt to use that reference will result in undefined behaviour (such as a crash of the app).
This situation is usually caused by either accidentally using the delete
operator on a Ref
object, or calling release()
without a prior matching retain()
.
NOTE: If required, AX_REF_LEAK_DETECTION
may be enabled in a release build, but only do this if and only if you absolutely need to, and disable it when you no longer need it.
More convenience at a (subjectively) low price, since there is some overhead.
The easiest mistake to make is forgetting to add a release()
call for every retain()
used, or vice versa (although rarer).
You can avoid this situation in your own code if you use the ax::RefPtr
, which is a relatively smart pointer, implemented in such a way to be similar to the std::shared_ptr
. What this does is handle the retain()
and release()
for you automatically.
For example:
Sprite*_mySprite = nullptr;
_mySprite = Sprite::create();
_mySprite->retain();
// do some stuff with this sprite
// then later...
_mySprite->release(); // Finally releases the reference, and if it happens to equal 0, it is freed from memory
_mySprite = nullptr;
or with macros:
Sprite*_mySprite = nullptr;
_mySprite = Sprite::create();
AX_SAFE_RETAIN(_mySprite);
someParentNode->addChild(_mySprite);
// do some stuff with this sprite
// then later...
AX_SAFE_RELEASE_NULL(_mySprite); // Finally releases the reference, and if it happens to equal 0, it is freed from memory
With RefPtr
, the above code is changed to the following:
ax::RefPtr<Sprite>_mySprite;
_mySprite = Sprite::create(); // RefPtr automatically calls `retain()`
someParentNode->addChild(_mySprite); // Use it as you normally would
// do some stuff with this sprite
// then later...
_mySprite = nullptr; // RefPtr automatically calls `release()` and becomes a nullptr to no longer point at any memory
As you can see from the above example, this addresses both situations of dangling references and memory leaks.
If you're thinking, "Why isn't this used throughout the Axmol engine code?", well, as mentioned earlier, there are overheads to using the ax::RefPtr
, the specifics of which may be a topic for another article. What is important is getting the most performance out of Axmol, so writing code to call retain()
and release()
for memory management of objects is the most appropriate thing to do in order to achieve that goal.
There are several containers implemented in Axmol which serve to hold Axmol objects (sub-classes of Ref
). These containers will automatically call retain()
and release()
on object insertion and removal respectively.
They are:
-
ax::Vector<Ref*>
(seeVector.h
): Holds a lit of items, similar tostd::vector
-
ax::Map<Key, Ref*>
(seeMap.h
): Holds key/value pairs, similar tostd::unordered_map
. The Axmol object must be stored in the value field, not the key field.
If you need to store a list of objects, for example, a bunch of sprites, in order to quickly access them later without having to search the child nodes of their parent object, then you may want to store them in a container. Let's say that you want to use a key/value store, so a map of items. If you attempted to use std::vector
, and to ensure there are no dangling references, you would write code similar to this:
std::vector<Sprite*> _sprites;
Sprite* sprite1 = Sprite::create();
addChild(sprite1);
_sprites.push_back(sprite1);
sprite1->retain();
Sprite* sprite2 = Sprite::create();
addChild(sprite2);
_sprites.push_back(sprite2);
sprite2->retain();
When you no longer need the sprites in the container, then you would do something like this:
for (auto&& sprite : _sprites)
{
sprite->release();
}
_sprites.clear(); // You have released all the sprites, so clear the container
Now, if you forget to call retain()
or release()
, you will end up with problems, as described previously.
To avoid these issues, and write less code, you can instead use the custom Axmol containers. For the example above, we will use ax::Vector
:
ax::Vector<Sprite*> _sprites;
Sprite* sprite1 = Sprite::create();
addChild(sprite1);
_sprites.pushBack(sprite1); // Note that we no longer need to call retain(), since it's done for us within this container
Sprite* sprite2 = Sprite::create();
addChild(sprite2);
_sprites.pushBack(sprite2);
When you no longer need the list of sprites, empty the list:
_sprites.clear(); // This automatically calls release() on all the items in the list, then empties the list.
The same applies if you remove specific entries from the list. For example, if we only want to remove the first sprite in the list, then we simply call:
_sprites.erase(0); // Remove first entry in the list. This automatically calls release() on the sprite object being removed