2
votes

I am working with ARC and Cocos2d 2.0 as static library (which does not use ARC and is compiled as a separate target). I translated an old project (made without ARC) and I am wondering whether declaring properties in this way has some potential retain cycle issue:

@interface PlayerData : NSObject <NSCoding> {
}


//Is ok to save this as if the game gets paused you may want to save this.
@property (readwrite, nonatomic) int numberOfHits;
@property (readwrite, nonatomic) bool everBeenHitInCurrentLevel;
@property (readwrite, nonatomic) int hitsForOneHearth;

I noticed that my various scenes build up memory over time. Also I added a CCLOG call in the release method of the CCLayer method (MyScene : CCLayer) and it never gets called. That's how I create the scene (which I replace using the "[CCDirector sharedDirector] replaceScene" method

+ (id) sceneWithLevelName:(LevelName)name
{
    CCScene *scene = [CCScene node];        
    ShooterScene * shooterLayer = [[self alloc] initWithId:name];
    [scene addChild:shooterLayer];


    return scene;    
}

EDIT: As I realized that I stupidly did NOT include an example with Objects and used only primitive data type I will here paste some snippets of elements in my scenes: CharacterSelection, ShooterScene and PlanetSelectionMenu:

//ShooterScene
@interface ShooterScene : CCLayer {
    HudLayer * hudLayer;
    ....
}

@property(readwrite, nonatomic) CCSpriteBatchNode* backgroundAndEnemiesBatchNode;
@property(readwrite, nonatomic) ShipEntity* playerShip;
... etc..


Please note that I do not declare member variables for properties like playerShip and backgroundAndEnemiesBatchNode beause, as far as I can understand, should suffice the property declaration (but please correct me if I am wrong or if the approach may cause issues). 

//CharacterSelectionScene
@interface CharacterSelectionScene : CCLayer {
    int currentlySelectedCharacterSpriteTag;
    CCSprite * lights;
    CCLayer * spritesLayer;    
    ...
}

//PlanetSelectionMenu
#import <Foundation/Foundation.h>
#import "cocos2d.h"

@interface PlanetSelectionMenu : CCLayer {
    CCLayer * backgroundLayer; // Added background images here
    CCLayer * alwaysPresentItems;
}

+ (id) scene;

@end

Please note that each time I go from PlanetSelectionMenu to CharacterSelectionScene -and vice versa- memories increases. However in this case I have not used any properties but "just" added objects (CCSprites) to layers and batchnodes.

EDIT 2: Here is what I see in Allocation when running through Menu->CharacterSelection->PlanetSelectionScene etc.. it seem that on avarage the LiveBytes are 4MB and, as I see only one scene at the time then I assume that there are no retain cycles. Then why do I get those nasty LOW memory messages?

enter image description here

1
The standard C-language number data types (int, bool, double, etc.) can't create a retain cycle because they don't point to anything. If you are expecting an object to be deallocated, and it's not, you probably have another object with a strong pointer to it. What objects have strong pointers to MyScene? Do you think they should deallocated?Aaron Brager
@AaronBrager good point. I added a better example including objects and not primitive data types. I am refering to the CCLayer of PlanetSelectionMenu (which is actually a scene - misleading name choosen) and CharacterSelectionScene as, when called replaceScene, the dealloc method does not print the CCLOG statement that I added.mm24

1 Answers

6
votes

Although the default property attribute is now strong, it can't be those properties that are causing a retain cycle as those are primitive types and would default to assign

Three other common methods of introducing retain cycles jump to mind:

  • Are you implementing the delegate pattern anywhere (@protocols). Are your delegate's always weak references where necessary?
    -(id) initWithDelegate:(id) target
    {
        ...
        _target = target;   //_target should be a weak reference
        ...
    }
  • Do any of your children nodes reference their parents?
    -(id) initWithParent:(CCNode*) parentNode
    {
        ...
        _parent = parentNode; //_parent should be a weak reference.
        ...
    }
  • Do any blocks reference self
   ^{[self callSomeMethod];}

should use a weak reference to self:

   __weak typeof(self) selfReference = self;
   ^{[selfReference callSomeMethod];}

I usually find that the best way to find leaks with ARC is NOT to use the Leaks tool, but the Allocations tool. Since all of my scenes tend to have the word "scene" in their symbol, I filter by the word scene.

Since you are using replaceScene you should only have one scene alive at a time (excepting during a transition), so you should only see one object in the object summary.

enter image description here

If you do have a scene that's hanging around, I usually find it's best to look at that objects retain history. From here, I pair up each retain with a corresponding release until I find the culprit(s) that are retaining and not releasing my scene. More often that not it's an obvious retain cycle with a block, or a property declared as strong instead of weak.

enter image description here