Why does this Code cause the EXC_BAD_ACCESS error? - iphone

Here's a piece of code I wrote for cleaning A string of unwanted chars and double spacing.
However, I seem to have misunderstood memory management somewhere and it keeps causing the EXC_BAD_ACCESS error. The Code works fine functionally when the release statements are removed but that would cause memory leaks.
-(NSString*) cleaningString:(NSString*) input {
NSCharacterSet* wantedCharacters=[[NSCharacterSet alloc] init];
wantedCharacters=[ NSCharacterSet
characterSetWithCharactersInString:#"qwertyuiopasdfghjklzxcvbnm"];
NSString* cleanStringOutput=[[NSString alloc] initWithString:#""];
NSString* currentLetter =[[NSString alloc] initWithString:#" "];
NSRange unwantedCharacters=[currentLetter rangeOfCharacterFromSet:wantedCharacters];
for (int i=0; i<input.length; i++) {
currentLetter=[NSString stringWithFormat:#"%c",[input characterAtIndex:i]];
unwantedCharacters=[currentLetter rangeOfCharacterFromSet:wantedCharacters];
doubleSpace=YES;
if (i<input.length-1) {
if (([currentLetter isEqualToString:#" "])&&([[NSString stringWithFormat:#"%c",[input characterAtIndex:i+1]] isEqualToString:#" "])) {
doubleSpace=NO;}
}
else {
if ([currentLetter isEqualToString:#" "]) {
doubleSpace=NO;
}
}
if ((unwantedCharacters.location!=NSNotFound)&&(doubleSpace))
{
cleanStringOutput=[NSString stringWithFormat:#"%#%#", cleanStringOutput, currentLetter];
}
}
if (cleanStringOutput.length>0){
if ([[NSString stringWithFormat:#"%c",[cleanStringOutput characterAtIndex:0]] isEqualToString:#" "]){
cleanStringOutput=[cleanStringOutput substringFromIndex:1];
}
}
[currentLetter release];
[wantedCharacters release];
[cleanStringOutput autorelease];
return cleanStringOutput;
}
Please forgive me if I just asked something painfully obvious.
P.S. And another question. Is it necessary to release the NSRange?

Right here
NSCharacterSet* wantedCharacters=[[NSCharacterSet alloc] init];
wantedCharacters=[ NSCharacterSet
characterSetWithCharactersInString:#"qwertyuiopasdfghjklzxcvbnm"];
You discard your original object and replace it with an autoreleased one
Which will crash when you call
[wantedCharacters release];
Do this
NSCharacterSet* wantedCharacters=[ NSCharacterSet
characterSetWithCharactersInString:#"qwertyuiopasdfghjklzxcvbnm"];
and forget the last
[wantedCharacters release];

There are several mistakes in your code that causes you to lose the reference to the allocated objects, and an example will follow, but a few things:
The easiest solution for all of them, is to use autorelease wherever you call alloc (and remove the release for those objects), for example:
NSString* cleanStringOutput=[[[NSString alloc] initWithString:#""] autorelease];
When you create a variable only to assign to it later, there is no need to allocate, for example:
NSCharacterSet* wantedCharacters; // no need for alloc here
wantedCharacters=[ NSCharacterSet characterSetWithCharactersInString:#"qwertyuiopasdfghjklzxcvbnm"];
In general - if you didn't allocate the object, you don't release it.

currentLetter=[NSString stringWithFormat:#"%c",[input characterAtIndex:i]];
Returns an autoreleased string - no need to alloc/init it above.
NSString* currentLetter =[[NSString alloc] initWithString:#" "];
So calling
[currentLetter release];
Will probably cause problems.

You alloc/init a wantedCharacters object, then reassign it using a convenience function. The reassignment creates a zombie with the first object.
The convenience function puts the new object instance into the autorelease pool.
Then you call release. Since it was only retained once, it gets deallocated.
Later, the autorelease pool calls release on it, but it has already been deallocated. This causes the crash.

Related

Memory Management Headache

I get leaks if I dont put it in dealloc. I get a crash EXC_BAD_ACCESS If I do. I cannot see anything wrong with this code. The bad access is pointed at [events release]. Have I made a mistake in the code below or is Instruments just having a laugh at my expense?
events is an NSArray
#interface EventsViewController : UITableViewController
{
#private
NSArray *events;
}
- (void)viewDidLoad
{
events = [[self getEvents] retain];
}
- (void)dealloc
{
[events release];
[super dealloc];
}
- (NSArray*)getEvents
{
NSMutableArray *response = [[[NSMutableArray alloc] init] autorelease];
//Some sql
while(sqlite3_step(statement) == SQLITE_ROW)
{
Event *event = [[[Event alloc] init] autorelease];
event.subject = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
[response addObject:event];
}
return response;
}
Update
A lot of you are saying the code is fine which is a plus. I dont manipulate events elsewhere - I have removed any code that does to try and single out the crash. Perhaps its in the parent view?
This is the click event that pushes the EventsViewController:
- (void)eventsClick:(id)sender
{
EventsViewController *eventsViewController = [[EventsViewController alloc] initWithNibName:#"EventsViewController" bundle:nil];
eventsViewController.anywhereConnection = anywhereConnection;
eventsViewController.contact = contact;
[[self navigationController] pushViewController:eventsViewController animated:YES];
[eventsViewController release];
}
The crash is actually happening when I return to the parent view. (I think it is considered a parent in this scenario). But perhaps the [eventsViewController release] just triggers dealloc in the EventViewController.
Have you considered just refactoring your code to use ARC? It works with iOS 4 and up and will make your life a lot easier. There are plenty of tutorials out there that will guide you how to do it, and will remove the need to manually figure out the nuances of memory management.
If your Events object has property 'subject' set as assign, then the results of stringWithUTF8String: will not be retained. (Same thing if Events is a C++ object.)
The stringWithUTF8String: method returns an auto-released object that will be released at the next turn of the event loop.
There is a huge difference when you reference a variable via "self", and when you don't.
When you use
events = [[self getEvents] retain];
the memory allocated in getEvents never gets stored in the class property and is basically a leak.
You need to use
self.events = [self getEvents]; // no need to use retain if property is correctly defined.
Then
[events release];
should work fine.
try putting
events = nil;
in dealloc.

Memory leak when using NSString inside for loop

I have 100 images in my resource bundle named like image1.jpg,image2.jpg.
Basically what i am trying to do is create path names to those images dynamically inside a for loop.
While testing in simulator,the images loaded fine and the app did not crash.But while testing the app with instruments i was shocked to see the heavy memory leak that was happening while i was creating the path1 object.
I am pasting the entire method here for reference
- (id)init {
self = [super init];
if (self) {
self.arrayImages = [[[NSMutableArray alloc] init] autorelease];
for(int i=1 ; i<100 ; i++){
NSString *str = [NSString stringWithFormat:#"Century%d",i];
NSString *path1 = [[NSBundle mainBundle] pathForResource:str ofType:#"jpg"];
[self.arrayImages addObject:path1];
}
}
return self;
}
As i have not made use of any alloc inside the loop i dont have any ownership and hence no right to release the object.What is the reason for this memory leak??
Kindly explain the problem and provide the necessary solution in order to fix it..
As always,any help is highly appreciated..
arrayImages is retaining path1, and so if you do not release arrayImages it will leak. How are you creating arrayImages, and are you releasing it anywhere?
Edited based on comments:
Make sure you release arrayImages in your -dealloc method like so: [arrayImages release]; (note the lack of self).
There is no leak in the code you've shown.
There are (at least) two possibilities:
You have a leak in code you didn't paste into your question
Everything is fine and Instruments gave you a false-positive
Your loop will create a lot of autoreleased variables. These won't be deallocated until after the loop has finished, but that's how it's supposed to work.
The reason for the leak would be this line right here:
NSString *str = [NSString stringWithFormat:#"Century%d",i];
By using convenience methods in Objective-C, what happens in the background is the following:
NSString *str = [[[NSString alloc] initWithFormat:#"Century%d", i] autorelease];
Not using alloc/init to create a weak reference is a misconception. You are always the owner of a created object, no matter how you create it. The convenience method simply does the alloc/init and autoreleases it for you.
Here's what I would suggest you do to avoid leaking memory:
- (id)init {
self = [super init];
if (self) {
self.arrayImages = [[[NSMutableArray alloc] init] autorelease];
NSAutoreleasePool *tmpPool = [[NSAutoreleasePool alloc] init];
for(int i = 1 ; i < 100 ; i++) {
NSString *str = [NSString stringWithFormat:#"Century%d",i];
NSString *path1 = [[NSString alloc] initWithString:[[NSBundle mainBundle] pathForResource:str ofType:#"jpg"]];
[self.arrayImages addObject:path1];
[path1 release];
}
[tmpPool drain];
}
return self;
}
Let me know if this works better for you.
-EDIT- Allocating the path1 object and releasing it after adding to arrayImages.

Why does this cause a crash?

I have these two buttons hooked up to these two methods (they're nearly identical)
-(void)moveOneImageNewer{
int num = [numberOfImage intValue];
num--;
numberOfImage = [[NSString stringWithFormat:#"%i",num] retain];
//Load the image
[self loadImage];
}
-(void)moveOneImageOlder{
int num = [numberOfImage intValue];
num++;
numberOfImage = [NSString stringWithFormat:#"%i",num];
//Load the image
[self loadImage];
}
If I hit either of them twice (or once each, basically if they get called a total of two times) I get an EXC_BAD_ACCESS. If I throw a retain on: numberOfImage = [[NSString stringWithFormat:#"%i",num]retain] it's fine though. Can someone explain why this is? I did an NSZombie on the instruments and traced it back to this stringWithFormat call. Thanks in advance!
+stringWithFormat: doesn't contain 'new', 'alloc', 'copy', or 'retain', so it should be expected that you have to retain the return value of it if you want the new NSString it creates to stick around.
Edited to include this handy link duskwuff kindly dug up: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-BAJHFBGH
If numberOfImage is a properly declared property, e.g.
#property (copy) NSString *numberOfImage;
and it was properly synthesized (in the #implementation section for the class):
#synthesize numberOfImage;
then you can do:
- (void) moveOneImageNewer
{
self.numberOfImage = [NSString stringWithFormat: #"%i", [self.numberOfImage intValue] - 1];
// Load the image
[self loadImage];
}
The property setter will take care of retaining the string and, if necessary, releasing the previous string.
FWIW, why on earth is numberOfImage a string? Why not a simple int?
numberOfImage is an instance variable or property of your class, right?
You are setting it to a stringWithFormat (which returns an auto-released NSString) without claiming ownership of that object (by calling retain).
If you do not retain it, it will get auto-released before the method is called again (and then the first line will fail, as it tries to access the previously set, now auto-released value).
Consider using properties, they have auto-generated memory management code (including releasing the old NSString when you set the new one).
You haven't retained the string object in "moveOneImageOlder", so that object gets autoreleased at the end of the event cycle and points to nothing. That's why you get the EXC_BAD_ACCESS next time you try to use it.
Use a retain to claim ownership of the NSString. Remember to release when you're done though (you can use properties to help you with this)
-(void)moveOneImageNewer{
int num = [numberOfImage intValue];
num--;
[numberOfImage release];
numberOfImage = [[NSString stringWithFormat:#"%i",num] retain];
//Load the image
[self loadImage];
}
-(void)moveOneImageOlder{
int num = [numberOfImage intValue];
num++;
[numberOfImage release];
numberOfImage = [[NSString stringWithFormat:#"%i",num] retain];
//Load the image
[self loadImage];
}
Add this in dealloc:
- (void)dealloc {
[numberOfImage release];
[super dealloc];
}
Well, the NSString class method "stringWithFormat" returns an autorelease NSString object if I'm right.
So the second call to your method would have numberOfImage pointing to nothing, as the autorelease NSString object it used to be pointing to has already been released and deallocated since you didn't retain it.
The part that is directly causing the crash is [numberOfImage intValue] when you call the method a second time, as you sending a message to an object (pointed to by numberOfImage) that no longer exist.

Dealloc objects of another class

Hi I generally create objects of another classes. can you please tel me if this wil be in the auto release pool? or should we release it manually.
if you init copy or new them you'll have to deallocate them if you put an autorlease with the allocation then they will be autoreleased
for example
Foo *foo = [[Foo alloc] init]; //you'll have release it somewhere yourself
And
Foo *foo = [[[Foo alloc] init] autorelease];// this will be autreleased
The simple case is : if you use init, you are responsible for releasing it, either by calling release or by calling autorelease.
e.g.
NSString *myString = [NSString alloc] init]; // You need to release this
...
[myString release]; // Now it's released - don't use it again!
or if you are going give it to someone else
NSString *myString = [NSString alloc] init]; // This needs releasing
...
return [myString autorelease]; // You are finished with it but someone else might want it
However, there's a few other cases.
NSString *myString = [NSString stringWithFormat:#"hi"];
This object is in the autorelease pool already - don't release it!
NSString *secondString = [myString copy];
This object needs releasing - it is not autoreleased.
Rule of thumb : Anything with init, copy or new in the name - you made it, you release it. Anything else will be autoreleased.

iphone Development - GDB error signal EXC_BAD_ACCESS

i get the signal error EXC_BAD_ACCESS when trying to retrieve the return output from the randomBallPick method, i probably do it wrong.
NSString *temp = [self randomBallPick];
upBall1.image = [UIImage imageNamed:temp];
Array (containers) retain/release items that are added/removed.
The object will receive release when it's removed from container with removeObjectAtIndex: so you need to retain it before it is removed and possibly autorelease since you are returning it from your method.
NSString * chosenFilename =
[[[imageArray objectAtIndex:chosen] retain] autorelease];
[imageArray removeObjectAtIndex:chosen];
return chosenFilename;
OK, can you try this, please?
NSString *chosenFilename = [[imageArray objectAtIndex:chosen] retain];
[imageArray removeObjectAtIndex:chosen];
return [chosenFilename autorelease];
All you need to do is:
NSString *chosenFilename = [[imageArray objectAtIndex:chosen] retain];
Since the objectAtIndex method returns an autorelease object.
There are several things wrong with this piece of code.
You are initializing your array of names once, but then you keep removing stuff from it... I'm not sure you want to do this, otherwise you'll start returning nil exclusively (after the 38th call...). You may want to re-fill your array in that case. Here's a better version of your routine (I think):
static NSMutableArray *imageArray = nil;
if (!imageArray.count) {
if (imageArray==nil) imageArray = [[NSMutableArray alloc] init];
for (int c = 0; c < 37; c++)
{
NSString *imageName = [NSString stringWithFormat:#"ball_%i.png", c];
[imageArray addObject:imageName];
}
}
// Now we are basically sure that imageArray.count > 0
assert(imageArray.count>0);
NSUInteger chosen = arc4random() % imageArray.count;
NSString *chosenFilename = [[imageArray objectAtIndex:chosen] retain];
[imageArray removeObjectAtIndex:chosen];
return [chosenFilename autorelease];
As others have said, you have to retain then autorelease the strings you extract from the array (because the array releases them upon removal).
Note also that you should call 'retain' on the string before removing it from the array. The string is already released after the removeObjectAtIndex: call... so it's already too late to retain it then.
As soon as you remove the object from the array, its retain count is probably zero, and it will get dealloced. Try doing
return [[chosenFilename] retain] autorelease]