Release of NSString Causing EXC_BAD_ACCESS - iphone

Thanks for your help on this one.
I am pulling a NSDictionary from a plist in my main bundle and am having troubles. Here is the code:
- (void)viewDidLoad {
// Pull in FAQ from Plist
NSString *strFAQPlist = [[NSBundle mainBundle] pathForResource:#"FAQs" ofType:#"plist"];
dictFAQList = [[NSDictionary alloc] initWithContentsOfFile: strFAQPlist];
// Create indexed array to hold the keys
arrFAQKeys = [[dictFAQList allKeys] retain];
// Release local vars
[strFAQPlist release];
[super viewDidLoad];
}
I feel like I should release NSString as I have already. The problem is, when I do so, I get an EXC_BAD_ACCESS error. When I comment that release out, everything works fine. Can someone explain to me why this is ocurring?
Thanks in advance!

pathForResource returns an autoreleased NSString.
Only call release if you've called an alloc/init method, copy method or retained it explicitly.
If you didn't create an object directly (or retained it) don't release it.

Related

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.

how to properly retain data read from plist?

I am stuck debugging a NSInvalidArgumentException. My latest suspicion is that I didn't retain the data read from plist properly so that it's occupied by some other object while I access it.
My plist structure is very complicated, it has 8 levels of arrays/dictionaries. I think I lost the memory when I try to access the lowest object.
I wonder if I have to retain every data element when I read the plist file or is it sufficient to just retain the top level object?
This is how I read:
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSError *error = [[[NSError alloc] init] autorelease];
NSArray *temp = (NSArray *)[[NSPropertyListSerialization
propertyListWithData:plistXML
options:NSPropertyListMutableContainersAndLeaves
format:nil
error:&error] retain];
self.dataPackage = [temp objectAtIndex:0];
dataPackage is declared as:
#interface rootViewController:UIViewController{
NSDictionary *dataPackage;
}
#property (retain) NSDictionary *dataPackage;
and synthesized:
#synthesize dataPackage;
Am I doing it right?
Thanks
Leo
I noticed 3 things:
You don't need to create an NSError object! NSPropertyListSerialization will return an error object if something fails. Just init with: NSError *error = nil;
You don't have to retain the (autoreleasing) temp-array, you obviously don't need the whole array after fetching the object at index 0.
[temp objectAtIndex:0] will crash when the array is empty!
Be sure to release the property var in dealloc with self.dataPackage = nil. Then everything is safe from memory management perspective.
Please go through the structure of Plist in TextEdit.... For more go through my previous post If you are using Xcode 4 then this might be the reason. The structure might got changed in xcode 4.

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.

NSArray Memory Management

For some reason when I release the NSArray I get the EXC_BAD_ACCESS exception. Here is the implementation:
-(void) loadAllAlphabets
{
NSBundle *bundle = [NSBundle mainBundle];
NSArray *imagesPath = [[NSArray alloc] init];
imagesPath = [bundle pathsForResourcesOfType:#"png" inDirectory:#"Images"];
alphabets = [[NSMutableArray alloc] init];
NSString *fileName = [[NSString alloc] init];
for(int i=0; i<= imagesPath.count -1 ; i++)
{
fileName = [[imagesPath objectAtIndex:i] lastPathComponent];
CCSprite *sprite = [CCSprite spriteWithFile:fileName];
sprite.userData = [[fileName stringByDeletingPathExtension] uppercaseString];
[alphabets addObject:sprite];
}
// release fileName
[fileName release];
fileName = nil;
[imagesPath release]; // this causes the application to crash with EXC_BAD_ACCESS
// imagesPath = nil;
}
UPDATE 1:
So, the problem was that although I was responsible for releasing the imagesPath object since I used alloc that soon become irrelevant when pathsForResourcesOfType returned an autorelease object.
This means I should not release the imagesPath object manually.
The following line should be used:
NSArray *imagesPath = [bundle pathsForResourcesOfType:#"png" inDirectory:#"Images"];
UPDATE 2:
Another question which is related to this post. In the following code I initialize a new NSMutableArray manually.
alphabets = [[NSMutableArray alloc] init];
Later I insert CCSprite (Cocos2d objects) into alphabets array. CCSprite are autorelease objects. Do I still have to release alphabets manually? Since, after some time all objects are released and memory will be returned but then what will be left inside alphabets NSMutable array?
I think the confusion is here:
NSArray *imagesPath = [[NSArray alloc] init];
imagesPath = [bundle pathsForResourcesOfType:#"png" inDirectory:#"Images"];
The first line creates a new object. This object really needs to be released.
The second line over-writes that object with a new, self-managed object. This does not need to be manually release.
This means that you're leaking the first imagesPath.
In general, you need to release an object if you alloc or copy it. And you shouldn't over-write an object before you release (or autorelease) its content.
general rule of thumb in memory management - you should release an object only if you obtain it using method that contains new, copy or alloc in it (standard method follow that rule and you should stick to it as well).
In your case you obtain imagesPath object using pathsForResourcesOfType: method which returns an autoreleased object so you must not release it yourself.
Edit: yes, you need to release alphabets object somewhere (for the same reason - you got it with alloc method).
Objective-c containers take an ownership of objects added to them, that us when objects are added to an array they get retained so it is guaranteed that their life time is at least as long as the life time of container. When you remove object from collection or collection itself is destroyed then its members get released (to compensate retain on add).
Also, you are leaking memory as you initialize imagesPath with an empty non-mutable array and then discard it when you assign he result of pathsForResources: to it. Just do this instead:
NSArray *imagesPath = [bundle pathsForResourcesOfType:#"png" inDirectory:#"Images"];
Same error with fileName. Not need to initialize it with an empty non mutable string.
And also do not release fileName since it is also an autoreleased object.

Should I release array returned from [NSMutableDictionary ValueForKey: ]

I have a NSMutableDictionary with the key being the first alphabet of the name of an object. The view is something like the 'Contacts' tab on iphone. Additionally user can select individual objects in the list.
In the code I find each selected object to process them further.
NSMutableArray *objectsToAdd = [[NSMutableArray alloc] init];
NSMutableArray *array = nil;
for (NSString *key in self.nameIndex) {
array = (NSMutableArray *)[searchedNameDictionary valueForKey:key];
for (Objects *eachObject in array) {
if (eachObject.objectIsSelected){
[objectsToAdd addObject:eachObject];
}
}
}
[array release];
-(void)dealloc()
{
[searchedNameDictionary release];
}
The app is crashing where I release searchedNameDictionary, with the message that the deallocated object is being referenced.
Now if in the code above, I remove [array release] the app works fine.
My question is does releasing 'array' is actually releasing the objects in searchedNameDictionary, which is what seems to be happening.
Would not releasing array cause memory leak?
You shouldn't release returned object unless they come from an alloc or copy method.
Returned objects are autoreleased otherwise, if you want to keep it around your should retain it right after receiving it.
array = (NSMutableArray *)[searchedNameDictionary valueForKey:key];
This returns an autoreleased object, thus you don't need to release it.
There are some other...issues with your code too, but mostly style things. Get rid of the [array release] and you're good to go as far as that issue is concerned.