I am having difficulty getting my head around memory management in the following segment of code on iPhone SDK 3.1.
// Create array to hold each PersonClass object created below
NSMutableArray *arrayToReturn = [[[NSMutableArray alloc] init] autorelease];
NSArray *arrayOfDictionaries = [self generateDictionaryOfPeople];
[arrayOfDictionaries retain];
for (NSDictionary *dictionary in arrayOfDictionaries) {
PersonClass *aPerson = [[PersonClass alloc] init];
for (NSString *key in [dictionary keyEnumerator]) {
if ([key isEqualToString:[[NSString alloc] initWithString: #"FIRST_NAME"]])
aPerson.firstName = [dictionary objectForKey:key];
else if ([key isEqualToString:[[NSString alloc] initWithString: #"LAST_NAME"]])
aPerson.lastName = [dictionary objectForKey:key];
}
// Add the PersonClass object to the arrayToReturn array
[arrayToReturn addObject: aPerson];
// Release the PersonClass object
[aPerson release];
}
return arrayToReturn;
The [self generateDictionaryOfPeople] method returns an array of NSDictionary objects. Each NSDictionary object has two keys "FIRST_NAME" and "LAST_NAME" with a person's first name and last name as the respective data. The code is looping through each dictionary object in the arrayOfDictionaries array and assigning the dictionary data to the relevant property of an aPerson (PersonClass) object. This object is then added to an array which is returned from this method.
When running instruments I am getting a leak for the dictionary objects contained in the arrayOfDictionaries array. The code within the [self generateDictionaryOfPeople] method is calling [dictionaryObject release] on each NSDictionary object as it is created and added to the array, which makes the retain count on the object 1 (as adding the object to the array would make the retain count 2, but then my release message decrements it back to 1).
I assume this leak is because I am never releasing the arrayOfDictionaries array, and thus the NSDictionary objects within the array are never released. If I attempt to release the array at the end of the above segment of code I get a "message sent to deallocated instance" error. I understand why this is occurring, because I am assigning the aPerson object data within a dictionary item (that I am subsequently releasing) but I don't know where else I can release the arrayOfDictionaries array. Any help would be greatly appreciated.
Thanks!
EDIT: Below is the implementation for [self generateDictionaryOfPeople]
- (NSArray *)generateDictionaryOfPeople {
NSMutableArray *arrayFromDatabase = [[[NSMutableArray alloc] init] autorelease];
// ** Query the database for data **
while ( there are rows being returned from the database ) {
// Declare an NSMutableDictionary object
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
// Loop through each column for that row
for ( while there are columns for this row ) {
columnTitle = title_of_column_from_database
columnData = data_in_that_column_from_database
// Add to the dictionary object
[dictionary setObject:columnData forKey:columnTitle];
// Release objects
[columnName release];
[columnTitle release];
}
// Add the NSMutableDictionary object to the array
[arrayFromDatabase addObject:dictionary];
// Release objects
[dictionary release];
}
// Return the array
return arrayFromDatabase;
}
Here,
if ([key isEqualToString:[[NSString alloc] initWithString: #"FIRST_NAME"]])
aPerson.firstName = [dictionary objectForKey:key];
else if ([key isEqualToString:[[NSString alloc] initWithString: #"LAST_NAME"]])
aPerson.lastName = [dictionary objectForKey:key];
Replace them with
if ([key isEqualToString:#"FIRST_NAME"])
aPerson.firstName = [dictionary objectForKey:key];
else if ([key isEqualToString:#"LAST_NAME"])
aPerson.lastName = [dictionary objectForKey:key];
The problem of the leak is you're creating 1 ~ 2 NSString-s per loop without -release-ing them. If you need constant NSString-s, just directly use them.
I am still getting the original leak due to not releasing the arrayOfDictionaries array.
That means you forgot to autorelease it in generateDictionaryOfPeople.
You need to review the memory management rules.
You are not releasing arrayFromDatabase. (The simplest way to avoid this kind of mistake is to use factories and autorelease as early as possible rather than defer releases manually. In this case, use [NSMutableDictionary dictionary] instead of [[NSMutableDictionary alloc] init].)
Related
I´m doing an app and I can´t get a mutable array to accept objects. I´v tried setting breakpoints to see what´s happening but it keeps saying that the mutable array is nil. Does anyone has an answer?
My code:
- (void)save:(id) sender {
// All the values about the product
NSString *product = self.productTextField.text;
NSString *partNumber = self.partNumberTextField.text;
NSString *price = self.priceTextField.text;
NSString *quantity = self.quantityTextField.text;
NSString *weigh = self.weighTextField.text;
NSString *file = [self filePath];
//Singleton class object
Object *newObject = [[Object alloc] init];
newObject.product = product;
newObject.partNumber = partNumber;
newObject.price = price;
newObject.quantity = quantity;
newObject.weigh = weigh;
//Array declaration
mutableArray = [[NSMutableArray alloc]initWithContentsOfFile: file];
[mutableArray addObject:newObject];
[mutableArray writeToFile:file atomically:YES];
}
While initWithContentsOfFile: can be called on an NSMutableArray, it was inherited from NSArray. The return value is an NSArray which is not mutable. If you want to add objects to your mutable array, you have to do something like this:
mutableArray = [[[NSMutableArray alloc] initWithContentsOfFile: file] mutableCopy];
[mutableArray addObject:newObject];
[mutableArray writeToFile:file atomically:YES];
Now, the addObject: call should work.
Best regards.
[NSMutableArray initWithContentsOfFile:] returns nil by default if the file can't be opened or parsed. Are you sure the file you're loading exists and is formatted correctly?
Try to check with break point on
mutableArray = [[NSMutableArray alloc]initWithContentsOfFile: file];
Line. Move your cursor on mutableArray if it shows you __NSArrayI that means it is an immutable array i.e. you cant update it and if it shows you __NSArrayM that means it is a mutable array and you can update this array.
In your case you're getting immutable array thats why you cant update it.
So you have two way to get mutable Array from this file -
Method:1
mutableArray = [[[NSMutableArray alloc] initWithContentsOfFile: file] mutableCopy];
Method:2
NSArray *anyArray = [[NSArray alloc]initWithContentsOfFile: file];
mutableArray = [[NSMutableArray alloc]initWithArray:anyArray];
In both case mutableArray woud be a mutable Array. You can update it.
So I have an array of NSDictionaries, each NSDictionary has a bunch of key/value pairs pertaining to aspects of a photo (from Flickr).
I'm making an app that has a UITableViewController whose cells should be each of the different categories of the photos. So in pseudocode, I'm trying to construct a new NSDictionary (with keys being categories of photos, values being the NSDictionaries of the photos that contains that key). I'm iterating through each NSDictionary in the initial array, getting the category tags, and saying, if my new NSDict doesn't contain this key, make a new key to an empty array. Then add the current NSDict to that array. I'm getting consistent errors, not sure why.
Here's the diluted code.
photoList = [FlickrFetcher photosWithTags:[NSArray arrayWithObjects: #"CS193p_SPoT", nil]];
NSLog(#"%#", photoList);
categories = [[NSDictionary alloc] init];
NSArray *temp = [[NSArray alloc] init];
for (id obj in photoList) {
temp = [[obj objectForKey:#"tags"] componentsSeparatedByString:#" "];
for (id string in temp) {
if (![categories objectForKey:string]) {
NSMutableArray *arr = [[NSMutableArray alloc] init];
[categories setObject:arr forKey:string];
//[arr release];
}
NSMutableArray *photos = [categories objectForKey:string];
[photos addObject:obj];
[categories setObject:photos forKey:string];
}
}
Thanks!
NSDictionary doesn't have a method setObject:forKey:. You need an NSMutableDictionary.
self.categories = [NSMutableDictionary dictionary];
Other than that, please do use Joost's excellent rewrite of your code.
SIGABRT, just so you know, most likely means that an assertion somewhere failed. In this case, it may be an assertion all the way down in CoreFoundation*; CF checks for mutability when you try to access a dictionary like that and causes an interrupt if the object isn't mutable.
*I have just learned about the CF source's availability recently and have been looking through it, so this may be just "new thing" bias and incorrect.
I don't notice any errors (syntax-errors, that is) in your code, however here is an updated piece of code which has been implemented a bit cleaner (and without memory leaks)
self.photoList = [FlickrFetcher photosWithTags:[NSArray arrayWithObjects: #"CS193p_SPoT", nil]];
NSLog(#"%#", photoList);
self.categories = [NSDictionary dictionary];
for (NSDictionary *obj in photoList) {
NSArray *temp = [[obj objectForKey:#"tags"] componentsSeparatedByString:#" "];
for (NSString *string in temp) {
NSMutableArray *photos = [categories objectForKey:string];
if (!photos) {
photos = [NSMutableArray array];
[categories setObject:photos forKey:string];
}
[photos addObject:obj];
}
}
If it's not working please tell us the exact warning, and were it is caused.
I am confused with releasing the object that I first alloc/initialized and then copied. As far as I understood the memory management manual I should release the object 2 times because by allocating it and then later copying it I should have retain value of 2?
So first release would lower it to one and second to 0?
I get message sent to dealloc object if I release it twice. If I do it once there are no problems in application, but there is one in my understanding of Objetive C mem. management :)))
The only explanation that I can think of is when you release an object from soecific context it will release it instantly no matter the value of retain count ?
In the snippet bellow xmlElement is the confusing one....
// LSnippet of code for TouchXML
for (CXMLElement *resultElement in resultNodes) {
NSMutableDictionary *xmlElement = [[NSMutableDictionary alloc] init];
// Create a counter variable as type "int"
int counter;
// Loop through the children of the current node
for(counter = 0; counter < [resultElement childCount]; counter++) {
// Add each field to the blogItem Dictionary with the node name as key and node value as the value
[xmlElement setObject:[[resultElement childAtIndex:counter] stringValue] forKey:[[resultElement childAtIndex:counter] name]];
}
// Add the blogItem to the global blogEntries Array so that the view can access it.
[tempReturnedElements addObject:[xmlElement copy]];
[xmlElement release];
//[xmlElement release]; but NOT!
}
UPDATE: whole method code:
+(void) runXPath:(NSString *)xPathExpression{
CXMLDocument *rssParser = [[[CXMLDocument alloc] initWithXMLString:xmlStringContent options:0 error:nil] autorelease];
// Create a new Array object to be used with the looping of the results from the rssParser
NSArray *resultNodes = NULL;
// Set the resultNodes Array to contain an object for every instance of an node in our RSS feed
resultNodes = [rssParser nodesForXPath:xPathExpression error:nil];
NSMutableArray *tempReturnedElements = [[NSMutableArray alloc]init];
// Loop through the resultNodes to access each items actual data
for (CXMLElement *resultElement in resultNodes) {
// Create a temporary MutableDictionary to store the items fields in, which will eventually end up in blogEntries
NSMutableDictionary *xmlElement = [[NSMutableDictionary alloc] init];
// Create a counter variable as type "int"
int counter;
// Loop through the children of the current node
for(counter = 0; counter < [resultElement childCount]; counter++) {
// Add each field to the blogItem Dictionary with the node name as key and node value as the value
[xmlElement setObject:[[resultElement childAtIndex:counter] stringValue] forKey:[[resultElement childAtIndex:counter] name]];
}
// Add the blogItem to the global blogEntries Array so that the view can access it.
[tempReturnedElements addObject:[xmlElement copy]];
//***** Crushes if I use:
//***** [tempReturnedElements addObject:[[xmlElement copy] autorelease]];
[xmlElement release];
}
[lotojuegosAppDelegate setMyReturnedXmlElements:[tempReturnedElements copy]];
[tempReturnedElements release];
}
Thanks in advance,
Luka
In [xmlElement copy], only the returned object (i.e. the copy) needs to be released. The receiver (xmlElement)'s ownership (retain count) is not changed.
So the correct way should be
NSDictionary* xmlElemCopy = [xmlElement copy];
[tempReturnedElements addObject:xmlElemCopy];
[xmlElemCopy release];
[xmlElement release];
The rule is pretty simple: for each alloc/init, copy or retain you must call exactly one release or autorelease when you want to give up the ownership. You've only called one alloc/init, so you may only call release once.
This line:
[tempReturnedElements addObject:[xmlElement copy]];
should be written as:
[tempReturnedElements addObject:[[xmlElement copy] autorelease]];
The reason is: copy gives you a new object. The retain count/ownership of the object pointed to by xmlElement is unchanged, but the new object now belongs to you. You're responsible for releasing it now. See the first paragraph in this answer: you called copy, you need to call release on the resulting object.
I was running Leaks tool and discovered a massive leak in my Dictionary mutableDeepCopy but I can't figure out what's wrong with the code. Any suggestions?
#interface RootViewController : UIViewController{
NSDictionary *immutableDictionary;
NSMutableDictionary *mutableDictionary;
}
Here is the line of code that's highlighted in Instruments
self.mutableDictionary = [self.immutableDictionary mutableDeepCopy];
Here is the method for creating a mutable copy of a Dictionary
#interface NSDictionary(MutableDeepCopy)
-(NSMutableDictionary *)mutableDeepCopy;
#end
Here is method implementation, I've highlighted the code that Leaks saids is leaking 100%
- (NSMutableDictionary *) mutableDeepCopy {
NSMutableDictionary *dictionaryToReturn = [NSMutableDictionary dictionaryWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for(id key in keys) {
id value = [self valueForKey:key];
id copy = nil;
if ([value respondsToSelector:#selector(mutableDeepCopy)]) {
copy = [value mutableDeepCopy];
} else if ([value respondsToSelector:#selector(mutableCopy)]) {
copy = [value mutableCopy]; //This is the Leak
}
if (copy == nil) {
copy = [value copy];
}
[dictionaryToReturn setValue:copy forKey:key];
}
return dictionaryToReturn;
}
You need to analyse this in light of Apple's Memory Management Rules.
Starting with this line:
self.mutableDictionary = [self.immutableDictionary mutableDeepCopy];
I would expect mutableDeepCopy to return an object I own, so at some point I need to release or autorelease it. e.g.
NSMutableDeepCopy* temp = [self.immutableDictionary mutableDeepCopy];
self.mutableDictionary = temp;
[temp release];
or
self.mutableDictionary = [[self.immutableDictionary mutableDeepCopy] autorelease];
So now we need to look at mutableDeepCopy. Because it has 'copy' in the name it needs to returned an "owned" object which, in practice means "forgetting" to release the returned object. You have already failed to do that when you create the returned object in the first line, since dictionaryWithCapacity: gives you an object you do not own. Replace it with
NSMutableDictionary *dictionaryToReturn = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
Now you own it.
It is important that you make your mutableDeepCopy obey the rules because it means you can treat the objects returned from mutableDeepCopy, mutableCopy and copy in exactly the same way. In all three cases you own the object copy that you insert into the array. Because you own it, you must release it or it'll leak as you found out. So, at the end of the loop, you need
[copy release];
That'll stop the leak.
How is your property declared? If is is retain or copy, then this doesn't leak.
Your problem is that the name mutableDeepCopy suggests that it returns a retained object, and not an autoreleased one as it actually does.
Edit:
And at the mutableDeepCopy itself, you need to release the copy variable after adding to the dictionary.
mutableCopy increments the retain count of the object, as does setValue:forKey:. This means that when dictionaryToReturn is dealloc'ed, the object that had mutableCopy called still has a retain count of one.
Try doing this instead:
copy = [[value mutableCopy] autorelease];
I'm trying hard to understand when and what I must relase in Cocoa Touch as it doesn't have garbage collection.
This code block is from apples iphone sample PeriodicElements and they release anElement and rawElementArray but not thePath, firstLetter, existingArray and tempArray?
I would have thought that at least tempArray and existingArray should be released.
Could some brainy person please explain to me why?
Thanks :)
- (void)setupElementsArray {
NSDictionary *eachElement;
// create dictionaries that contain the arrays of element data indexed by
// name
self.elementsDictionary = [NSMutableDictionary dictionary];
// physical state
self.statesDictionary = [NSMutableDictionary dictionary];
// unique first characters (for the Name index table)
self.nameIndexesDictionary = [NSMutableDictionary dictionary];
// create empty array entries in the states Dictionary or each physical state
[statesDictionary setObject:[NSMutableArray array] forKey:#"Solid"];
[statesDictionary setObject:[NSMutableArray array] forKey:#"Liquid"];
[statesDictionary setObject:[NSMutableArray array] forKey:#"Gas"];
[statesDictionary setObject:[NSMutableArray array] forKey:#"Artificial"];
// read the element data from the plist
NSString *thePath = [[NSBundle mainBundle] pathForResource:#"Elements" ofType:#"plist"];
NSArray *rawElementsArray = [[NSArray alloc] initWithContentsOfFile:thePath];
// iterate over the values in the raw elements dictionary
for (eachElement in rawElementsArray)
{
// create an atomic element instance for each
AtomicElement *anElement = [[AtomicElement alloc] initWithDictionary:eachElement];
// store that item in the elements dictionary with the name as the key
[elementsDictionary setObject:anElement forKey:anElement.name];
// add that element to the appropriate array in the physical state dictionary
[[statesDictionary objectForKey:anElement.state] addObject:anElement];
// get the element's initial letter
NSString *firstLetter = [anElement.name substringToIndex:1];
NSMutableArray *existingArray;
// if an array already exists in the name index dictionary
// simply add the element to it, otherwise create an array
// and add it to the name index dictionary with the letter as the key
if (existingArray = [nameIndexesDictionary valueForKey:firstLetter])
{
[existingArray addObject:anElement];
} else {
NSMutableArray *tempArray = [NSMutableArray array];
[nameIndexesDictionary setObject:tempArray forKey:firstLetter];
[tempArray addObject:anElement];
}
// release the element, it is held by the various collections
[anElement release];
}
// release the raw element data
[rawElementsArray release];
// create the dictionary containing the possible element states
// and presort the states data
self.elementPhysicalStatesArray = [NSArray arrayWithObjects:#"Solid",#"Liquid",#"Gas",#"Artificial",nil];
[self presortElementsByPhysicalState];
// presort the dictionaries now
// this could be done the first time they are requested instead
[self presortElementInitialLetterIndexes];
self.elementsSortedByNumber = [self presortElementsByNumber];
self.elementsSortedBySymbol = [self presortElementsBySymbol];
}
They create rawElementsArray by sending +alloc to the class, therefore this object is owned by the code in the sample above and must be released. Similarly with anElement. Note that thePath and tempArray are not created by sending +alloc, +new or -copy messages, therefore the calling code is not responsible for the lifetime of those objects. Please have a look at this collection of Cocoa memory management articles:
http://iamleeg.blogspot.com/2008/12/cocoa-memory-management.html
The reason you don't have to release tempArray is because it's been allocated and then autoreleased right away. Autorelease is a method of scheduling a release call sometime in the future, so that the caller of an API doesn't have to do any explicit releasing of the result.
Matt Dillard has provided a detailed explanation of Objective C's memory management strategy and has explained it much better than I can.
The convention is that when you create an object using a class method it should have been autoreleased. This means that at the end of the run loop when the autorelease pool is flushed these objects will be released. However, if you create something using +alloc] -init] or -copy, -mutableCopy or +new (which is the same as +alloc] -init]) then it will not have been autoreleased.
For example:
NSArray *array1 = [NSArray arrayWithObject:#"foo"];
NSArray *array2 = [[NSArray alloc] initWithObject:#"foo"];
Array1 will be autoreleased and you don't need to worry about it. Array2 will need to be manually released. Or alternatively you could do:
NSArray *array2 = [[[NSArray alloc] initWithObject:#"foo"] autorelease];
Which is pretty much what +arrayWithObject: does.
Of course this leads to an important consideration with the lifetime of instance variables. If you create the instance variable as with array2 then it will be fine as it has a retain count of 1. However, array1 will need to be retained otherwise it will be autoreleased at the end of the runloop, giving it a retain count of 0 and so it will be freed and you will be left with a dangling pointer.