dealloc problem with instance variables - iphone

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.

Related

Do I need to send release to my instance variable in the dealloc method? (iOS)

In my class dealloc method I have
- (void) dealloc
{
[searchField release];
[super dealloc];
}
Where searchField is defined in the class variables.
#interface SearchCell : UITableViewCell
{
UISearchBar *searchField;
id delegate;
}
The class is used with the following way:
if (indexPath.section == 0)
{
SearchCell *mycell = [[SearchCell alloc] init];
[cell setDelegate:self];
return [mycell autorelease];
}
searchField is created here:
- (id) init
{
self = [super initWithFrame:CGRectZero];
[self create];
return self;
}
- (void) create
{
searchField = [[UISearchBar alloc] initWithFrame:CGRectZero];
searchField.autocorrectionType = UITextAutocorrectionTypeNo;
[self addSubview:searchField];
}
Do I need to use [searchField release]; in my dealloc? The application crashes with message: "*[UISearchBar respondsToSelector:]: message sent to deallocated instance *".
No, don't release it there. Since you return it with "autorelease", the next run through your event loop will automatically decrease the retain count.
When you finally hit your "dealloc" method, the retain count is already 0 and your app will raise an exception.
You need to be aware of object ownership rules. In manual retain & release environment (as opposed to ARC) the rules for Objective-C objects can be remembered with the acronym NARC.
If you have a reference to an object that you created fresh using new or alloc, or any object that you called retain on, or any object you created by calling copy or mutableCopy on another, or any object created by any methods that start with any of these names (e.g. newInstance), then
you own that object and before you are deallocated you must release it.
But, if you didn't do any of those things, then you do not own that object, and must not release it. source
In your case - you don't show the code where you set the value searchField, but from the crash I'd imagine you never take ownership of the object. Show the code where you assign to searchField and we'll all know for sure.
EDIT: You do take ownership by creating it with alloc. You need to keep the release there in dealloc. Is it being released from somewhere else? Or is the cell itself possibly getting overreleased?

objective-c memory management clarification

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

iphone using cocos2d get EXC_BAD_ACCESS

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!

Do I need to set these variables to nil in viewDidLoad as I have in this code?

- (void)viewDidUnload {
self.GPSArray = nil;
self.accelerometerArray = nil;
self.headingArray = nil;
self.managedObjectContext = nil;
self.locationManager = nil;
self.pointLabel = nil;
self.accelerometerLabel= nil;
self.headingLabel= nil;
self.startStop = nil;
self.lastAccelerometerReading = nil;
self.lastGPSReading = nil;
self.lastHeadingReading = nil;
}
- (void)dealloc {
[GPSArray release];
[accelerometerArray release];
[headingArray release];
[managedObjectContext release];
[locationManager release];
[pointLabel release];
[accelerometerLabel release];
[headingLabel release];
[startStop release];
[lastAccelerometerReading release];
[lastGPSReading release];
[lastHeadingReading release];
[super dealloc];
}
The reason viewDidUnload is called, is that your view is being released and any view resources should be freed.
So, you only need to free view related items.
In your case it looks like you'd only need to free the UILabels that are probably in your view. If they were marked as IBOutlets and not in assign properties, you'd want to release the memory used by them:
self.pointLabel = nil;
self.accelerometerLabel= nil;
self.headingLabel= nil;
That also means, that in viewDidLoad if you are setting up the other properties you want to make sure they are not being allocated again if they are there already as it can be called again if the view is unloaded and then reloaded again.
The reason this would be called is if the view controller received a memory warning. You can test this memory warning in the simulator to see how viewDidUnload and viewDidLoad are called.
You don't have to, and if you run this, you'll be wasting some substantial execution time. Using the property method to set a property to nil is the same thing as releasing the property, with the caveat that some extra stuff may or may not happen, depending on how you've set up the setter methods.
So let's walk through this code. At the end of your viewDidUnload method, all of your properties are now nil. The object is then deallocated, and your object attempts to release a dozen nil objects or so. Now, the Objective-C runtime is pretty smart, and if you send a message to nil, (surprise surprise) nothing will happen.
So you've basically got a dozen lines that do absolutely nothing.
no, you shouldn't.
if you do as if the above code, you are wasting effort.
By setting them to nil in viewDidUnload, they will be going thru a process of release and retain automatically, which means, by the time the code get to dealloc, they are actually released and nil, and you are doing another release there for the nil.
Releasing nil object might be erratic.
So, ignore those in viewDidUnload.

"Swapping" a UIView Instance variable - cannot dealloc "previous" view

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.