views:

277

answers:

1

I want to organize somehow my iPhone game's level-views, but I simply cannot (without expanding Object Allocations). I made a really "skeleton" of my code (this game has 2 levels, the goal is to release the iPhone display). I just cannot dealloc the previous level, so Instrunments shows incrementing BGTangramLevel instances.

Please, take a look on it, I need some helpful ideas on designing (my 3rd question on it).

viewcontroller.h

@interface compactTangramViewController : UIViewController
{
    //The level.
    BGTangramLevel *currentLevel;
    UIColor *levelColor;
}

//It is to be just a reference, therefore I use assign here.
@property (nonatomic, retain) BGTangramLevel *currentLevel;

-(void) notificationHandler: (NSNotification*) notification;
-(void) finishedCurrentLevel;

@end

viewcontroller.m

@implementation compactTangramViewController
@synthesize currentLevel;

//Initializer functions, setting up view hierarchy.
-(void) viewDidLoad
{   

    //Set up levelstepper.
    levelColor = [UIColor greenColor];

    //Set up "state" classes.
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationHandler:) name:@"finishedCurrentLevel" object:nil]; 

    //Attach level 1.
    currentLevel = [BGTangramLevel levelWithColor: levelColor frame:self.view.frame];
    [self.view addSubview:currentLevel];

    [super viewDidLoad];

}

//Release objects.
-(void) dealloc
{
    [currentLevel release];
    [super dealloc];
}

//Notification handling.
-(void) notificationHandler: (NSNotification*) notification
{
    //Execute level swap.
    if ([notification name] == @"finishedCurrentLevel") [self finishedCurrentLevel];
}

-(void) finishedCurrentLevel
{
    //Remove previous level.
    [currentLevel removeFromSuperview];
    //[currentLevel release];

    //Step level.
    if (levelColor == [UIColor greenColor]) levelColor = [UIColor blueColor]; else levelColor = [UIColor greenColor];

    //Attach level 2.
    currentLevel = [BGTangramLevel levelWithColor: levelColor frame:self.view.frame];
    [self.view addSubview:currentLevel];

}
@end

BGTangramLevel.h

@interface BGTangramLevel : UIView
{
    BOOL puzzleCompleted; 
}

//Initializer.
+(BGTangramLevel*)levelWithColor: (UIColor*) color frame: (CGRect) frame;

//Test if the puzzle is completed.
-(void) isSolved;

@end

BGTangramLevel.m

@implementation BGTangramLevel

//Allocated instance.
+(BGTangramLevel*)levelWithColor: (UIColor*) color frame: (CGRect) frame
{
    BGTangramLevel *allocatedLevel = [[BGTangramLevel alloc] initWithFrame:frame];
    allocatedLevel.backgroundColor = color;
    return allocatedLevel;
}

//Finger released.
-(void) touchesEnded: (NSSet*)touches withEvent: (UIEvent*)event
{
    //The completement condition is a simple released tap for now...
    puzzleCompleted = YES;
    [self isSolved]; 
}

//Test if the puzzle is completed.
-(void) isSolved
{
    //"Notify" viewController if puzzle has solved.
    if (puzzleCompleted) [[NSNotificationCenter defaultCenter] postNotificationName:@"finishedCurrentLevel" object:nil]; 
}

-(void) dealloc
{
    NSLog(@"Will ever level dealloc invoked."); //It is not.
    [super dealloc];
}

@end

So what should I do? I tried to mark autorelease the returning level instance, release currentLevel after removeFromSuperview, tried currentLevel property synthesized in (nonatomic, assign) way, but Object Allocations still grow. May I avoid Notifications? I'm stuck.

+2  A: 

You need to follow retain/release rules more closely. You definitely should not experimentally add retain and release and autorelease in places just to find something that works. There's plenty written about Cocoa memory management already, I won't repeat it here.

Specifically, BGTangramLevel's levelWithColor:frame: method should be calling [allocatedLevel autorelease] before returning allocatedLevel to its caller. It doesn't own the object, it's up to the caller to retain it.

You also need to know the difference between accessing an instance variable and accessing a property. Cocoa's properties are just syntactic-sugar for getter and setter methods. When you reference currentLevel in your view controller you are dealing with the instance variable directly. When you reference self.currentLevel you are dealing with the property.

Even though you've declared a property, currentLevel = [BGTangram ...] simply copies a reference into the variable. In viewDidLoad, you need to use self.currentLevel = [BGTangram ...] if you want to go through the property's setter method, which will retain the object (because you declared the property that way). See the difference?

I think your leak is happening in finishedCurrentLevel. If you had used self.currentLevel = [BGTangram ...], the property's setter method would be called, which would release the old object and retain the new one. Because you assign to the instance variable directly, you simply overwrite the reference to the old level without releasing it.

Calling [currentLevel release] in the dealloc method of your view controller is correct.

benzado
Sounds really promising. I'm looking forward to test it. Yes, the return [allocatedLevel autorelease]; skipped by an accident. These allocators/initializers should return autorelease marked objects, thats clear. Most of the answers suggested to use synthesized accessors, and I belived that I do use them. I have tried to implement the accessors by myself to ensure release/retain, but was useless since I never called them with a simple currentLevel=[... assignment. Hope self.currentLevel=[... helps. Thanks again.
Geri
One more question. Supposing I get/set trough accessors, do I have manually retain once more currentLevel before call removeFromSuperview (as documentation claims)? Hmm... think not, cos' I want to have BGTangramLevel instance deallocated there.
Geri
A view retains its subviews, so `addSubview:` is an implicit `retain` and `removeFromSuperview` is an implicit `release`.However, since your controller takes responsibility for retaining and releasing currentLevel, you don't have to care if anybody else does.The documentation is just noting that it is a common mistake to call `removeFromSuperview` without retaining the view first, in that case it will be immediately deallocated.
benzado