I am making a simple game project involving the use of Cocos2d. Now as defined by Ray Wenderlich's example, i have completed the whole tutorial but added an extra bit of code myself to check total number of melons, when they reach 3, i replace screen with "You Win" screen to notify the user that he has won using [[CCDirector sharedDirector] replaceScene:gameoverscreen];.
The problem is that i get EXC_BAD_ACCESS everytime i call this from ccTouchEnded coz my condition is checked here. But the same thing works if i use [[CCDirector sharedDirector] pushScene:gameoverscreen];
Cant understand what the problem is!!
the code for gameoverscreen screen is:
#import "GameOverScene.h"
#import "HelloWorldScene.h"
#implementation GameOverScene
#synthesize _layer = layer;
- (id)init {
if ((self = [super init])) {
self._layer = [GameOverLayer node];
[self addChild:layer];
}
return self;
}
- (void)dealloc {
[layer release];
layer = nil;
[super dealloc];
}
#end
#implementation GameOverLayer
#synthesize _label = label;
-(id) init
{
if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
self._label = [CCLabel labelWithString:#"" fontName:#"Arial" fontSize:32];
label.color = ccc3(0,0,0);
label.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:label];
[self runAction:[CCSequence actions:
[CCDelayTime actionWithDuration:3],
[CCCallFunc actionWithTarget:self selector:#selector(gameOverDone)],
nil]];
}
return self;
}
- (void)gameOverDone {
[[CCDirector sharedDirector] replaceScene:[[[HelloWorld alloc] init] autorelease]];
}
- (void)dealloc {
[label release];
label = nil;
[super dealloc];
}
#end
and the Header file of GameoverScene contains the following!
#import "cocos2d.h"
#interface GameOverLayer : CCColorLayer {
CCLabel *label;
}
#property (nonatomic, retain) CCLabel *_label;
#end
#interface GameOverScene : CCScene {
GameOverLayer *layer;
}
#property (nonatomic, retain) GameOverLayer *_layer;
#end
i call the scene from HelloWorld class using the following syntax!
GameOverScene *gameoverscene = [GameOverScene node];
[gameoverscene._layer._label setString:#"You WON!"];
[[CCDirector sharedDirector] pushScene:gameoverscene];
I see several issues in your code.
One is the CCLabel object, you initialize it as autorelease object using cocos2d's static initializer:
self._label = [CCLabel labelWithString:#"" fontName:#"Arial" fontSize:32];
But in the dealloc method you release it even though its an autorelease object:
- (void)dealloc {
[label release];
label = nil;
[super dealloc];
}
You should not release the label since it is set to autorelease by cocos2d! This is a guaranteed crash!
Then you make things more complicated than needed:
[[CCDirector sharedDirector] replaceScene:[[[HelloWorld alloc] init] autorelease]];
The alloc/init/autorelease is completely superfluous because you can simply write [HelloWorld scene] if the HelloWorld class has a +(id) scene method (it normally should). If not, then use [HelloWorld node]. Always prefer cocos2d's static autorelease initializers before using alloc/release on cocos2d objects. The only time you ever need to alloc a cocos2d class is when you explicitly don't add it as a child to some other node, which is rare.
Finally, this is very bad style:
-(id) init
{
if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {
If the super implementation of initWithColor calls [self init] - which is often the case and even if not, could change with future releases of cocos2d - it would call your implementation of init, resulting in an endless loop (stack overflow). To fix this simply either rename your init method or call [super init] and provide the parameters some other way, usually there will be a property or setter method to do so.
And a minor issue: Apple advises against using leading underscores as member variable prefix. In fact, many other compiler vendors advice against that too since often system internal variables use one or two underscores as prefix. The cocos2d style with trailing underscores is preferred which would have you write label_ instead of _label.
EXEC_BAD_ACCESS means you are using data that has been released. Does the youwin scene use data from the current scene? If so, it needs to retain the data. When replaceScene: is called, the current scene is not held in memory but when pushScene: is called, both scenes remain in memory.
EDIT:
Let's say you have two scenes, A and B. When you call pushScene:, A continues to exist in memory and B is added. When you call replaceScene:, A is removed and no longer exists, only the B scene. That is why A's data would disappear, but only when replacing.
The general rule when it comes to memory dealing is to release whatever you alloced or retained. In your case you are instantiating a CCLabel object with a convenience method (thus, not calling alloc) and you are not retaining it. So, that [label release] in your dealloc method must not be there in this case.
I also account with such thing,the reason for it may be you release something that are autorelease,so you can try it again by not to release some object in the dealloc method!
Related
I am trying to make a simple iphone app and I have been playing around with features and recently, delegates. I am just confused with regards to memory management because apparently "good code" makes my app crash with exc_bad_access.
I have an object with two data members and implementation empty for now.
#implementation semester: NSObject{
NSInteger ID;
NSString *name;
}
then my delegate method:
- (void) receiveSemester:(semester *)newSemester {
[test setText:newSemester.name];
}
and a view that is used as a form which has:
#interface addSemesterController : UIViewController {
id<ModalViewDelegate> delegate;
UITextField *txtName;
UILabel *prompt;
UIButton *ok;
UIButton *cancel;
}
all objects are made properties and synthesized in the application file. Here is the method that used the delegate:
- (IBAction) okClick:(id)sender{
// create semester object and return it
semester *created = [[semester alloc] init];
created.name = txtName.text;
[delegate receiveSemester:created];
[self dismissModalViewControllerAnimated:YES];
}
And my dealloc method looks like this:
- (void)dealloc {
/*
[txtName dealloc];
[prompt dealloc];
[ok dealloc];
[cancel dealloc];
*/
[super dealloc];
}
With the deallocs of the objects contained in the form commented out, my app runs ok. However, when I uncomment them, I receive the exc_bad_access error in my delegate protocol:
// in main view controller
- (void) receiveSemester:(semester *)newSemester {
[test setText:newSemester.name];
// test is a UILabel
}
I tried the zombie method and it says that the label calls a released object. I am not releasing my semester object in the "form" controller, and even if I was the delegate function is called before deallocating the view.
Clearly I should not be releasing the objects in the dealloc method, I am just unclear in the why I shouldn't.
Again, thanks in advance for the help.
Use release to release the variables instead of calling dealloc on variables, due to this you are having issue -
- (void)dealloc {
[txtName release];
[prompt release];
[ok release];
[cancel release];
[super dealloc];
}
try writing
[txtName release];
[prompt release];
[ok release];
[cancel release];
instead of dealloc and these objects will be deallocated properly
A problem happens when I was trying to release one of my instance variables and reassign it a new value.
I would like to release the address that a instance variable points to, and re-assign a new value to it.
The code look like this:
The .h
#interface MapPageController : UIViewController<MKMapViewDelegate> {
AddressAnnotationManager *addAnnotation;
}
- (IBAction) showAddress;
#property (nonatomic, retain) AddressAnnotationManager *addAnnotation;
The .m
#synthesize addAnnotation;
- (IBAction) showAddress {
if(addAnnotation != nil) {
[mapView removeAnnotation:addAnnotation];
[addAnnotation release]; // this generates the problem
addAnnotation = nil;
}
addAnnotation = [[AddressAnnotationManager alloc] initWithCoordinate:location];
addAnnotation.pinType = userAddressInput;
addAnnotation.mSubTitle = addressField.text;
}
However, with [addAnnotation release], a EXC_BAD_ACCESS always comes along if the process runs through it.
Thus I printed out the memory address in the dealloc of AddressAnnotationManager:
- (void)dealloc {
NSLog(#"delloc Instance: %p", self);
[super dealloc];
}
I turned on Zombie, the console gave me something like this:
2010-10-10 17:02:35.648 [1908:207] delloc Instance: 0x46c7360
2010-10-10 17:02:54.396 [1908:207] -[AddressAnnotationManager release]: message sent to deallocated instance 0x46c7360*
It means the code reaches dealloc before the problem occurs.
I have checked all the possible places where I could release addAnnotation. However, I could not find any.
Does anyone happen to find what the problem is?
I suspect that this is not the whole code involving the addAnnotation variable. Most likely [mapView removeAnnotation:addAnnotation];, which releases addAnnotation, already makes the reference count drop to zero. Do you have something like this in your code somewhere ?
[mapView addAnnotation:addAnnotation];
[addAnnotation release];
If so, then you have transfered the complete ownership of addAnnotation to the mapView and you don't need to release it in showAddress any more, which means that removeAnnotation: is enough.
This is a simplified version of the problem I'm facing now.
I've made 2 empty CCScene 1 & 2 and added CCLayer 1 & 2 onto their respective scene.
I also added an touches function to switch from scene 1 to scene 2 using CCDirector's replacescene.
However, dealloc was never called during the replace scene.
// scene & layer 2 are exactly the same as 1
#implementation MainScene
-(void)dealloc {
NSLog(#"scene dealloc");
[super dealloc];
}
-(id)init {
self = [super init];
if (self) {
layer = [[MainLayer alloc]init];
[self addChild:layer];
[layer release];
NSLog(#"test: %i", [layer retainCount]); //1
}
return self;
}
#implementation MainLayer
-(void)dealloc {
NSLog(#"layer dealloced");
[super dealloc];
}
-(id)init {
self = [super init];
if (self) {
self.isTouchEnabled = YES;
NSLog(#"test %i", [self retainCount]); //1
}
return self;
}
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"test %i", [self retainCount]); //2 --> ????
[[CCDirector sharedDirector] replaceScene:[[SecScene alloc]init]];
}
furthermore, the NSLog reported the retaincount of the layer to be 2 when I touch the screen. Is this even suppose to be happening? Can anyone probably tell me what I've done wrong or is it just my misunderstanding that retainCount needs to be 0 before dealloc is called?
This problem is causing my main game program to crash just by switch between various scenes/layers with just static sprites (and some minor actions) over and over again.
I'm not too knowledgeable about cocos2d's contract but shouldn't you release the SecScene you alloc in ccTouchesBeganon this line: [[CCDirector sharedDirector] replaceScene:[[SecScene alloc]init]]
I don't see any reason why replaceScene wouldn't retain, so now the SecScene has a retain count of two when it should have one.
More importantly, if you added MainScene in a similar way that would explain why its retain count is one higher than you'd like it to be, so it will never get deallocated.
Also, the dealloc gets only called rarely I found - so it is hard to test and get it invoked...
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.
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.
I have a line of UILabel text in a UIView which is regularly updated via NSTimer. This code is supposed to write a status item near the bottom of the screen every so often. The data comes from outside of its control.
My app runs out of memory really fast because it seems the UILabel is not being released. It seems that dealloc is never called.
Here is a very compressed version of my code (error checking etc removed for clarity.):
File:SbarLeakAppDelegate.h
#import <UIKit/UIKit.h>
#import "Status.h"
#interface SbarLeakAppDelegate : NSObject
{
UIWindow *window;
Model *model;
}
#end
File:SbarLeakAppDelegate.m
#import "SbarLeakAppDelegate.h"
#implementation SbarLeakAppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
model=[Model sharedModel];
Status * st=[[Status alloc] initWithFrame:CGRectMake(0.0, 420.0, 320.0, 12.0)];
[window addSubview:st];
[st release];
[window makeKeyAndVisible];
}
- (void)dealloc
{
[window release];
[super dealloc];
}
#end
File:Status.h
#import <UIKit/UIKit.h>
#import "Model.h"
#interface Status : UIView
{
Model *model;
UILabel * title;
}
#end
File:Status.m
This is the where the problem lies. UILabel just does not seem to be released, and quite possible the string as well.
#import "Status.h"
#implementation Status
- (id)initWithFrame:(CGRect)frame
{
self=[super initWithFrame:frame];
model=[Model sharedModel];
[NSTimer scheduledTimerWithTimeInterval:.200 target:self selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
return self;
}
- (void)drawRect:(CGRect)rect
{
title =[[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 12.0f)];
title.text = [NSString stringWithFormat:#"Tick %d", [model n]] ;
[self addSubview:title];
[title release];
}
- (void)dealloc
{
[super dealloc];
}
#end
File: Model.h (this and the next are the data sources, so included only for completeness.) All it does is update a counter every second.
#import <Foundation/Foundation.h>
#interface Model : NSObject
{
int n;
}
#property int n;
+(Model *) sharedModel;
-(void) inc;
#end
File: Model.m
#import "Model.h"
#implementation Model
static Model * sharedModel = nil;
+ (Model *) sharedModel
{
if (sharedModel == nil)
sharedModel = [[self alloc] init];
return sharedModel;
}
#synthesize n;
-(id) init
{
self=[super init];
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(inc) userInfo:nil repeats:YES];
return self;
}
-(void) inc
{
n++;
}
#end
The problem is that you are never removing the UILabel from the Status UIView. Let's take a look at your retain counts in drawRect:
(void)drawRect:(CGRect)rect {
title =[[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 12.0f)];
Here, you have created a UILabel with alloc, which creates an object with a retain count of 1.
[self addSubview:title];
[title release];
Adding the UILabel to Status view increases title's retain count to 2. The following release results in a final retain count of 1. Since the object is never removed from its superview, the object is never deallocated.
Essentially, you are adding one UILabel on top of another, each time the timer is fired, until memory runs out.
As suggested below, you should probably create the UILabel once when the view loads, and just update the UILabel's text with [model n].
As a housekeeping note, you might also want to make sure that you are properly deallocating any left over objects in your dealloc methods. 'model' and 'title' should be released in Status' dealloc, just as 'model' should be in SbarLeakAppDelegate.
Hope this helps.
Edit [1]:
It sounds like you have the memory issue pretty well handled at this point. I just wanted to suggest another alternative to the two timers you are using.
The timer you have running in your Status object fires every .2 seconds. The timer which actually increments the 'model' value, n, fires only once each second. While I believe you are doing this to ensure a more regular "refresh rate" of the Status view, you are potentially re-drawing the view 4 or 5 times per second without the data changing. While this is may not be noticeable because the view is fairly simple, you might want to consider something like NSNotification.
With NSNotification, you can have the Status object "observe" a particular kind of notification that will be fired by the Model whenever the value 'n' changes. (in this case, approximately 1 per second).
You can also specify a callback method to handle the notification when it is received. This way, you would only call -setNeedsDisplay when the model data was actually changed.
There are 2 problems with your code.
Problem 1
In -drawRect you add a subview to the view hierarchy every time the view is drawn. This is wrong for 2 reasons:
Every time the view is drawn, the number of subviews increases by 1
You are modifying the view hierarchy at draw time - this is incorrect.
Problem 2
Timers retain their targets. In the initializer for your Status object, you create a timer which targets self. Until the timer is invalidated, there is a retain cycle between the timer and the view, so the view will not be deallocated.
If the approach of using a timer to invalidate the view is really the correct solution to your problem, you need to take explicit steps to break the retain cycle.
One approach to doing this is to schedule the timer in -viewDidMoveToWindow: when the view is being put in a window [1], and invalidate the timer when the view is being removed from a window.
[1] Having the view invalidate itself periodically when it isn't displayed in any window is otherwise pointless.
Instead of calling -setNeedsDisplay with your NSTimer in the view controller, why not create a method that calls "title.text = [NSString stringWithFormat:#"Tick %d", [model n]] ;"? That way instead of re-creating the label every time the timer fires you can just update the value displayed.