EXC_BAD_ACCESS error help - objective-c

I am new to objective-c and i cannot figure out how memory handling works exactly in this language. Here is some code i wrote from a turorial and i am confused why when i uncomment the [filePath release] i get an error even though the method is finished. I read some articles on how memory handling works but i cant see what i am doing wrong here.
#import "saaving_dddaaattaViewController.h"
#implementation saaving_dddaaattaViewController
#synthesize field;
-(NSString *)pathOfFile {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsFolder = [paths objectAtIndex:0];// paths[0] = documents directory
return [documentsFolder stringByAppendingFormat:#"myfile.plist"];
}
-(void)applicationWillTerminate:(NSNotification *)notification {
NSLog(#"Saving data...");
NSMutableArray *array = [[NSMutableArray alloc]init];
[array addObject:field.text];
[array writeToFile:[self pathOfFile] atomically:YES];
[array release];
}
- (void)dealloc {
[field release];
[super dealloc];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)viewDidLoad {
NSString *filePath = [self pathOfFile];
if ([[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
NSLog(#"File[%#] does exist.", filePath);
NSArray *array = [[NSArray alloc]initWithContentsOfFile:filePath];
field.text = [array objectAtIndex:0];
[array release];
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:app];
//[filePath release];// <--- commented out release
[super viewDidLoad];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end

Memory Management can be confusing at first. Only release objects that you have created. This is almost only if you use the words alloc, init, retain.
The problem in your case is that you do not actually own the object, it is autoreleased, because you did not alloc, init or retain it. This is fine, but it is not your job to release it, so don't worry about it.
If you get "EXC_BAD_ACCESS" errors later, it might be helpful to use NSZombies to help find where you are releasing incorrectly. They work by placing a "zombie" in memory wherever you release an object so it is easier to tell what the problem is.
EDIT: For example, say you have:
NSString *foo = [[NSString alloc] initWithString:#"foo"];
NSString *bar = [NSString stringWithString:#"bar"];
You would have to release foo, by calling: [foo release]; at some point, but you would not have to release bar because it you did not use alloc to allocate memory for it. This goes for any type of object, not just NSString. A great website explaining this can be found here.

Related

NSUserDefaults array not saving properly

I have a problem i cant get my head around. I have an instance of NSUserDefaults and have stored some values in there. One of these values is an array that contains the players scores (its a game) to be used in another display. After the game is finished, I update the array with the new score and reassign it to the NSUserDefaults, but for some reason it doesn't seem to be behaving correctly and in debug when i try to print the count into he console, its always 0.
Here is the code in question:
// // SAMHighScoreInputViewController.m // Tappity // // Created by Sam on 29/05/13. // Copyright (c) 2013 Sam. All rights reserved. //
#import "SAMHighScoreInputViewController.h"
#import "SAMScoreObject.h"
#interface SAMHighScoreInputViewController ()
#end
#implementation SAMHighScoreInputViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self; }
- (void)viewDidLoad {
self.defaults = [NSUserDefaults standardUserDefaults];
self.nameScoreLabel.text = [NSString stringWithFormat:#"%# - %#", [self.defaults objectForKey:#"lastName"], [self.defaults objectForKey:#"lastScore"]];
self.nameTextField.text = [self.defaults objectForKey:#"lastName"];
[super viewDidLoad]; // Do any additional setup after loading the view. }
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated. }
- (IBAction)textFieldValueChanged:(UITextField *)sender {
self.nameScoreLabel.text = [NSString stringWithFormat:#"%# - %#", self.nameTextField.text, [self.defaults objectForKey:#"lastScore"]];
}
- (IBAction)submitButtonTapped:(UIButton *)sender {
SAMScoreObject *score = [[SAMScoreObject alloc] init];
score.playerName = self.nameTextField.text;
score.score = [self.defaults integerForKey:#"lastScore"];
NSArray *scores = [self.defaults objectForKey:#"scoresArray"];
NSMutableArray *updatedArray = [scores mutableCopy];
[updatedArray addObject:score];
NSArray *updatedArrayScores = [updatedArray copy];
[self.defaults setObject:updatedArrayScores forKey:#"scoresArray"];
[self.defaults setObject:[self.defaults objectForKey:#"scoresArray"] forKey:#"scoresArray"];
[self.defaults synchronize];
NSLog(#"%#, %#", [self.defaults objectForKey:#"lastScore"], [self.defaults objectForKey:#"lastName"]);
NSLog(#"%i", [[self.defaults objectForKey:#"scoresArray"] count]);
[self dismissViewControllerAnimated:YES completion:nil];
} #end
You need to serialize your SAMScoreObject into an NSData object of some form before you can store it in the NSUserDefaults store.
From the documentation:
A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.
The fact that you're storing an NSArray object isn't good enough, because the objects in that array aren't one of the supported types.
Where is the scoresArray ever created? You should probably lazily create it when you attempt to update it. For example:
NSArray *scores = [self.defaults objectForKey:#"scoresArray"];
if(scores == nil)
{
scores = [NSArray array];
}
NSMutableArray *updatedArray = [scores mutableCopy];

Objective-C Array of Objects

Although experienced with OOP, I am an absolute newbie with Objective-C. I have the following code:
// header files have been imported before this statement...
CCSprite *treeObstacle;
NSMutableArray *treeObstacles;
#implementation HelloWorldLayer {
}
-(id) init
{
// create and initialize our seeker sprite, and add it to this layer
treeObstacles = [NSMutableArray arrayWithObjects: nil];
for (int i=0; i<5; i++) {
treeObstacle = [CCSprite spriteWithFile: #"Icon.png"];
treeObstacle.position = ccp( 450-i*20, 100+i*20 );
[self addChild:treeObstacle];
[treeObstacles addObject: treeObstacle];
}
NSLog (#"Number of elements in array = %i", [treeObstacles count]);
return self;
}
- (void) mymethod:(int)i {
NSLog (#"Number of elements in array = %i", [treeObstacles count]);
}
#end
The first NSLog() statement returns "Number of elements in array = 5". The problem is that (although treeObstacles is a file-scope variable) when calling the method "mymethod", I'll get an EXC_BAD_ACCESS exception.
Can anybody please help me?
Thanks a lot
Christian
you created treeObstacles by
treeObstacles = [NSMutableArray arrayWithObjects: nil];
which will return an autoreleased object, and you didn't retain it so it will be released soon
you have to retain it by calling retain on it
[treeObstacles retain];
of simple create it by
treeObstacles = [[NSMutableArray alloc] init];
and you need to remember to release it when done like
- (void)dealloc {
[treeObstacles release];
[super dealloc];
}
you need to read more about management in Objective-C
https://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/MemoryManagement.html
or use ARC so no need to worry retain/release anymore
http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
another problem, you need to call [super init] in your init method
- (id)init {
self = [super init];
if (self) {
// your initialize code
}
}
otherwise your object won't initialize properly

Objective C memory management issue

I'm having this issue with some objective C code to load the images on a IKImageBrowserView.
I'm following the image browser example from apple, but I still fail at some point and Im guessing its memory management.
Here is some of the code:
/* Our datasource object */
#interface myImageObject : NSObject
{
NSString *_path;
}
#end
#implementation myImageObject
- (void)dealloc
{
[_path release];
[super dealloc];
}
/* our datasource object is just a filepath representation */
- (void)setPath:(NSString *)path
{
NSLog(#"setting path for %#",path);
if(_path != path)
{
[_path release];
_path = [path retain];
}
}
Now, It seems I'm properly retaining the path value within the object.
now on to the controller code:
-(IBAction)loadMosaicImages:(id)sender
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *urls = [openFiles() retain];
NSInteger n;
n = [urls count];
NSURL *url = [urls objectAtIndex:0];
[self parsePathForImages:[url path]];
[urls release];
[pool drain];
}
- (void)updateDatasource
{
NSLog(#" UDS-> _importedImages length : %#",[_importedImages count]);
//-- update our datasource, add recently imported items
[_images addObjectsFromArray:_importedImages];
//-- empty our temporary array
[_importedImages removeAllObjects];
NSLog(#" UDS-> _images length : %#",[_images count]);
//-- reload the image browser and set needs display
[_imageBrowser reloadData];
}
-(void)parsePathForImages:(NSString *)path{
NSLog(#"Directory within thread method is %#",path);
NSArray *content = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
myImageObject *p;
for(int i=0;i<content.count;i++)
{
// NSLog(#"%#",(NSString *)[content objectAtIndex:i]);
NSLog(#"Complete : %#",[path stringByAppendingPathComponent:(NSString *)[content objectAtIndex:i]]);
/* add a path to our temporary array */
p = [[myImageObject alloc] init];
[p setPath:[path stringByAppendingPathComponent:[content objectAtIndex:i]]];
[_importedImages addObject:p];
[p release];
}
[self updateDatasource];
}
and that's all the relevant code. _images and _importedImages are NSMutableArrays alloced and inited in the awake from nib method, and openFiles() is a static method that opens an NSOpenpanel and returns an NSArray of the paths.
the debug output for this does this:
Directory within thread method is /Users/cromestant/Code/images/
Complete : /Users/cromestant/Code/images/1.jpg
setting path for /Users/cromestant/Code/images/1.jpg
Complete : /Users/cromestant/Code/images/2.jpg
setting path for /Users/cromestant/Code/images/2.jpg
Complete : /Users/cromestant/Code/images/3.jpg
setting path for /Users/cromestant/Code/images/3.jpg
.
.
.
then stops crashes at the first line of the method updateDataSource, on the NSLog with an 'EXEC_BAD_ACCESS'so where am I going wrong with the memory management?
I seem to be creating an autoreleasePool so ht I have time to retain somewhere else, I release my objects.. I really don't have a clue where the problem could be.
thanks in advance.
Actually, I think your problem might not be memory management. It might be here:
NSLog(#" UDS-> _importedImages length : %#",[_importedImages count]);
It should be
NSLog(#" UDS-> _importedImages length : %d",[_importedImages count]);
because the count method returns an integer, not an object.

Locating a memory leak/source of over-release crach

Can anyone help me figure out where I should be releasing exerciseArray? I get a crash in dealloc and when I release just before calling sortedArray. This code is called many times.
//Set exerciseArray
review.exerciseArray = [[workout assignedExercises] allObjects];
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(#"viewWillAppear");
NSString *workoutName = [event.assignedWorkout valueForKey:#"name"];
self.title = workoutName ;
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"index"
ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[sortedArray release];
sortedArray= [exerciseArray sortedArrayUsingDescriptors:sortDescriptors];
NSLog(#"*****SORTEDARRAY****** %d",[sortedArray retainCount]);
for (Exercise *ex in sortedArray){
NSLog(#"%# %# %#",ex.name , ex.repCount, ex.weight);
}
NSLog(#"*********************");
[sortedArray retain];
[self.tableView reloadData];
}
- (void)dealloc {
[super dealloc];
[managedObjectContext release];
[event release];
}
First things first: You should move the [super dealloc] call to the end of your dealloc method. Handle your custom variables first, and then the last thing you do is push the dealloc up to the superclass to handle the rest of the cleanup.
Now, I'm concerned about the [sortedArray release] and [sortedArray retain] in there. What you should be doing, to save you the semantics of implementing retain/release on your sortedArray ivar, is declaring a property for sortedArray like so:
// this goes in your #interface
#property (nonatomic, retain) NSArray *sortedArray;
// this goes in your #implementation
#synthesize sortedArray;
Now you can set sortedArray easily without worrying about retaining or releasing:
// note the use of self, this is required
self.sortedArray = [exerciseArray sortedArrayUsingDescriptors:sortDescriptors];
You can remove the release and retain calls now, as this is handled automatically. Make sure to add a line to your dealloc method to clean up the variable, too..
self.sortedArray = nil;
..which will call release on your array for you.
EDIT: This also applies to exerciseArray, which was your actual question. Wherever there's a Cocoa class involved, you can simplify memory management using #property (retain) and #synthesize. For NSInteger and other primitive types, or when you don't want to hold an owning reference to the object, use #property (assign) instead.

Does this code leak?

I just ran my app through the Leaks in Instruments and I am being told that the following code causes leaks, but I don't see how.
I allocate some NSMutableArrays in my viewDidLoad with this code:
- (void)viewDidLoad {
[super viewDidLoad];
self.currentCars = [[NSMutableArray alloc] init];
self.expiredCars = [[NSMutableArray alloc] init];
}
Then I populate these arrays inside of my viewWillAppear method with the following:
[self.currentCars removeAllObjects];
[self.expiredCars removeAllObjects];
for (Car *car in [self.dealership cars]) {
if ([car isCurrent])
[self.currentCars addObject:car];
if ([car isExpired])
[self.expiredCars addObject:car];
}
And later in the code I release these arrays here:
- (void) viewWillDisappear:(BOOL)animated {
if (currentCars != nil) {
[currentCars release], currentCars = nil;
}
if (expiredCars != nil) {
[expiredCars release], expiredCars = nil;
}
[super viewWillDisappear:animated];
}
Any ideas? Thanks!
Your leak is here:
self.currentCars = [[NSMutableArray alloc] init];
self.expiredCars = [[NSMutableArray alloc] init];
Assuming that you declared property accessores like this:
#property(nonatomic, retain) NSMutableArray *currentCars;
#property(nonatomic, retain) NSMutableArray *expiredCars;
In my opinion, the best way to find leaks (other than using Instruments) is to keep track of the retain count manually.
If you were to do that with for example currentCars, you would find your leak easily. Here is what happens:
self.currentCars = [[NSMutableArray alloc] init];
// The 'init' makes the retain count 1.
// 'self.currentCars = ..' translates to the setCurrentCars: method.
// You probably did not implement that method yourself,
// but by synthesizing your property it is automatically implemented like this:
- (void)setCurrentCars:(NSMutableArray *)array {
[array retain]; // Makes the retain count 2
[currentCars release];
currentCars = array;
}
// In your viewWillDisappear: method
[currentCars release], currentCars = nil; // Makes the retain count 1 so the object is leaked.
The solution is simple. Use this:
NSMutableArray *tempMutableArray = [[NSMutableArray alloc] init];
self.currentCars = tempMutableArray;
[tempMutableArray release];
A little sidenote. You shouldn't release your objects in viewWillDisappear:. The recommended place to do that is dealloc. So your code would be:
- (void)dealloc {
[currentCars release], currentCars = nil;
[expiredCars release], expiredCars = nil;
[super dealloc];
}
The problem is (probably) that you are using the property accessors for the initial setting of the arrays in -viewDidLoad. Since well-implemented property accessors will retain the object, you are getting 1 retain from the +alloc and another retain from assigning it. To fix this, you should release your arrays after assigning them or use [NSMutableArray array] to get an autoreleased one to use for your initial assignments.
Unless you're doing something very odd in currentCars, expiredCars, dealership or cars, no, there's no leak there.
Instruments' pointer to the location of a leak isn't necessarily where the object is actually leaked, per se. If I were to guess, I'd say you're probably neglecting to release either currentCars or expiredCars in your dealloc method.