memory mgmt problem in complex dictionary - iphone

I hate asking memory management questions - all the good answers
are variations on RTFM. But this one is stumping me.
I have a (relatively) complex dictionary in my model class,
where each key points to an array of arrays. I constantly add and delete
items to it, depending on state. Each "item" is an array.
- (void)addToDictionary:(NSNumber *)itemID {
// get what we need (associated array of arrays & key) from the incoming ID
NSArray *incomingArray = [self getArrayFromID:[itemID intValue]];
NSString *myKey = [incomingArray objectAtIndex:0];
NSMutableArray *myNewArray = [[NSMutableArray alloc] init];
// case 1: this key is not in the dictionary yet
if ([[myDict allKeys] containsObject:myKey] == NO) {
[myNewArray addObject:incomingArray];
[myDict setObject:myNewArray forKey:myKey];
// case 2: key already there; add new array to its array
} else {
myNewArray = [NSMutableArray arrayWithArray:[myDict objectForKey:myKey]];
[myNewArray addObject:incomingArray];
[myDict removeObjectForKey:myKey];
[myDict setObject:myNewArray forKey:myKey];
}
// why isn't this line working??
[myNewArray release];
}
My question is the last line. I allocated this array to help me
work with the dictionary, and now I don't need it any more.
But the program will crash if I release it, and work just fine
if I comment that line out. What am I missing?
TIA

In case two you don't own the returned array. So only release it in case 1. And don't create something you'll not use. The NSMutableArray pointer will get assigned to some other data in case 2, not the one you've allocated. So you can't release something you don't own.
- (void)addToDictionary:(NSNumber *)itemID {
NSArray *incomingArray = [self getArrayFromID:[itemID intValue]];
NSString *myKey = [incomingArray objectAtIndex:0];
NSMutableArray *myNewArray;
if ([[myDict allKeys] containsObject:myKey] == NO) {
// Create when you need it
myNewArray = [[NSMutableArray alloc] init];
[myNewArray addObject:incomingArray];
[myDict setObject:myNewArray forKey:myKey];
// release when you're done with it
[myNewArray release];
} else {
myNewArray = [NSMutableArray arrayWithArray:[myDict objectForKey:myKey]]; // you don't own it!
[myNewArray addObject:incomingArray];
[myDict removeObjectForKey:myKey];
[myDict setObject:myNewArray forKey:myKey];
}
// why isn't this line working??
//[myNewArray release];
// because in case 2 it's not pointing to the right memory
}
Hope it works,
ief2

Related

Modifying content from new dictionary also modifies the parent dictionary data

As I have a requirement to add similar objects into the array, I have created new dictionary in such a way.
NSMutableDictionary* existingStepDict = [[[arrayForSteps objectAtIndex:0] mutableCopy] autorelease];
[arrayForSteps addObject:existingStepDict];
[existingStepDict release];
Now, what happens here is that later when I change something in any one of the dictionary, the other one also gets updated. I require both these dictionaries to behave independently.
For that I went through Deep-copy of dictionaries whose code is like this.
NSMutableDictionary* existingStepDict = [[[arrayForSteps objectAtIndex:0] mutableCopy] autorelease];
NSMutableDictionary* destination = [NSMutableDictionary dictionaryWithCapacity:0];
NSDictionary *deepCopy = [[NSDictionary alloc] initWithDictionary:existingStepDict copyItems: YES];
if (deepCopy) {
[destination addEntriesFromDictionary: deepCopy];
[deepCopy release];
}
//add Properties array to Steps Dictionary
[arrayForSteps addObject:destination];
But this too didn't reflect the difference. I know I am making some minor mistake here.
But could some one help me getting my result?
Thanks a lot!
There's an easy way to get a full deepcopy of an NSDictionary o NSArray using the NSCoding (serialization) protocol.
- (id) deepCopy:(id)mutableObject
{
NSData *buffer = [NSKeyedArchiver archivedDataWithRootObject:mutableObject];
return [NSKeyedUnarchiver unarchiveObjectWithData: buffer];
}
In this way you can duplicate any object plus all the obects it contains in a single step.
when I need a mutable deep copy of a NSDictionary I create a Category with this method:
- (NSMutableDictionary *)mutableDeepCopy
{
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for (id key in keys) {
id oneValue = [self valueForKey:key];
id oneCopy = nil;
if ([oneValue respondsToSelector:#selector(mutableDeepCopy)]) {
oneCopy = [oneValue mutableDeepCopy];
} else if ([oneValue respondsToSelector:#selector(mutableCopy)]) {
oneCopy = [oneValue mutableCopy];
}
if (oneCopy == nil) {
oneCopy = [oneValue copy];
}
[returnDict setValue:oneCopy forKey:key];
}
return returnDict;
}
EDIT
and searching the web I found this, I haven't tested
NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);

How do I find (not remove) duplicates in an NSDictionary of NSArrays?

The title pretty much says it all, but just to clarify: I have an NSMutableDictonary containing several NSMutableArrays. What I would like to do is find any value that is present in multiple arrays (there will not be any duplicates in a single array) and return that value. Can someone please help? Thanks in advance!
Edit: For clarity's sake I will specify some of my variables:
linesMutableDictionary contains a list of Line objects (which are a custom NSObject subclass of mine)
pointsArray is an array inside each Line object and contains the values I am trying to search through.
Basically I am trying to find out which lines share common points (the purpose of my app is geometry based)
- (NSValue*)checkForDupes:(NSMutableDictionary*)dict {
NSMutableArray *derp = [NSMutableArray array];
for (NSString *key in [dict allKeys]) {
Line *temp = (Line*)[dict objectForKey:key];
for (NSValue *val in [temp pointsArray]) {
if ([derp containsObject:val])
return val;
}
[derp addObjectsFromArray:[temp pointsArray]];
}
return nil;
}
this should work
If by duplicates you mean returning YES to isEqual: you could first make an NSSet of all the elements (NSSet cannot, by definition, have duplicates):
NSMutableSet* allElements = [[NSMutableSet alloc] init];
for (NSArray* array in [dictionary allValues]) {
[allElements addObjectsFromArray:array];
}
Now you loop through the elements and check if they are in multiple arrays
NSMutableSet* allDuplicateElements = [[NSMutableSet alloc] init];
for (NSObject* element in allElements) {
NSUInteger count = 0;
for (NSArray* array in [dictionary allValues]) {
if ([array containsObject:element]) count++;
if (count > 1) {
[allDuplicateElements addObject:element];
break;
}
}
}
Then you have your duplicate elements and don't forget to release allElements and allDuplicateElements.

What is causing memory leaks?

What is causing the leaks in this code? I really cannot understand it.
On thes lines:
1: NSMutableArray * days = [[NSMutableArray alloc]init];
2: [dic setObject:days forKey:key];
3: [days addObject:value];
The whole method:
-(void) addValueToDictionary: (NSMutableDictionary *) dic withValue: (NSNumber *) value forKey: (NSString *) key {
NSMutableArray * days = [dic objectForKey:key];
if (days == nil) {
NSMutableArray * days = [[NSMutableArray alloc]init];
[days addObject:value];
[dic setObject:days forKey:key];
[days release];
days = nil;
}
else {
[days addObject:value];
}
}
BR
//Christoffer
Check to make sure that dic is released. You should NSLog retainCount before where you think the final releases are and make sure they are 1 right before the final release.
Also, run a Build and Analyze to make sure you are releasing correctly. The built in Build and Analyze doesn't find as many leaks as running scan-build with all checks, so look into installing scan-build into Xcode as well.
Using an external Xcode Clang Static Analyzer binary, with additional checks
You should be getting a warning about re-declaring days. This may be throwing the leak check off if you are using the static analyser. Modified method below. Mostly coding style changes with a little defensive coding added.
-(void) addValueToDictionary: (NSMutableDictionary *) dic withValue: (NSNumber *) value forKey: (NSString *) key
{
if (nil == dic || nil == key || nil == value) return; // bail out on nil parameters
if (![dic objectForKey:key]) {
NSMutableArray * days = [[NSMutableArray alloc] init];
[dic setObject:days forKey:key];
[days release];
}
[[dic objectForKey:key] addObject:value];
}
Have you tried to change the name of the variable NSMutableArray *days within the if? Don't you get a warning because of that?
There's nothing wrong with that particular piece of code (other than the slightly questionable redefinition of days in the inner scope). Somewhere else you are retaining but forgetting to release the object you put in the dictionary.
change NSMutableArray initialization to...
NSMutableArray * days = [NSMutableArray array];

Table crashes when sorting the data multiple times

I have a tableview with a navigationBar with a segmentedControl on the top of the view. I have set up the segmentedControl with buttons that sort the table by either "FirstName" or "LastName". It works perfectly the first 2-4 of times you press the sorting buttons, but then the app crashes.
The debugger and console seem to be of no help finding the source of the bug. Does anyone see any glaring mistakes in my code?
Here is my code below, let me know if you have any questions. Thanks!
- (IBAction)sortingSegmentAction:(id)sender{
NSString *keyToSortBy = [NSString alloc];
if([sender selectedSegmentIndex] == 0)
{
self.sortingSegmentActionPressed = 0;
keyToSortBy = #"FirstName";
}
else if([sender selectedSegmentIndex] == 1)
{
self.sortingSegmentActionPressed = 1;
keyToSortBy = #"LastName";
}
//Create the sort descriptors
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:keyToSortBy ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
//Sort allSubItams by the values set in the sort descriptors
NSArray *sortedArray;
self.sortedArray = [allSubItems sortedArrayUsingDescriptors:sortDescriptors];
//Recreate the data structure by putting the newly sorted items into a dictionary sorted by inital letters.
NSDictionary *eachItemList; //A DICTIONARY FOR PUTTING ALL THE DATA FOR EACH ITEM IN IT'S OWN SECTION
NSMutableDictionary *tempSectionedDictionaryByFirstLetter = [[NSMutableDictionary alloc] init];
for (eachItemList in sortedArray) //eachElementList is a dictionary with a section for each item
{
NSDictionary *aDictionary = [[NSDictionary alloc] initWithDictionary:eachItemList];
NSString *firstLetterString;
firstLetterString = [[aDictionary valueForKey:keyToSortBy]substringToIndex:1];
NSMutableArray *existingArray;
if (existingArray = [tempSectionedDictionaryByFirstLetter valueForKey:firstLetterString])
{
[existingArray addObject:eachItemList];
} else {
NSMutableArray *tempArray = [NSMutableArray array];
[tempSectionedDictionaryByFirstLetter setObject:tempArray forKey:firstLetterString];
[tempArray addObject:eachItemList];
}
[aDictionary release];
[eachItemList release];
}
//Set the data source for the table (sectionedDictionaryByFirstLetter) to tempSectionedDictionaryByFirstLetter.
self.sectionedDictionaryByFirstLetter = tempSectionedDictionaryByFirstLetter;
NSMutableArray *keyArray = [[NSMutableArray alloc] init];
[keyArray addObjectsFromArray:[[self.sectionedDictionaryByFirstLetter allKeys] sortedArrayUsingSelector:#selector(compare:)]];
self.keys = keyArray;
[self.tableView reloadData];
[keyArray release];
[tempSectionedDictionaryByFirstLetter release];
}
Don't release eachItemList at the end of your loop. You did not explicitly allocate it in this context so you shouldn't release it.
The for (object in array) loop gives you a reference to the object in the array, not a copy. By sending a release message to this reference, you are decrementing the retain count of this object while it is still in the array. After a few times (depending on how many times the object has been retained, NSArray for example retains objects when they are added to the array) it's retain count will reach 0, and it will then become deallocated and you'll get crashes regarding unrecognised selectors or EXC_BAD_ACCESS and possibly other kinds of errors.

Leaking Memory on iPhone :(

I'm a beginner at C, Obj-C and the iPhone, and I'm trying getting to grips with a lot of terminology used. I hope one of ye can help with a problem I have been struggling with for a few days now.
My code below is a method which call up a nib containing a search field and a table. The table is populated from a search of the array created for 'theList' below. Using 'Instruments', I am getting a Leak at the line:
NSDictionary *theItem = [NSDictionary dictionaryWithObjectsAndKeys:clientName,#"Name",clientId,#"Id",nil]; , but I can't figure out why :(
I know it's probably a difficult question to answer, but if any one can be of any help!
- (void)editClient:(id)sender {
if (pickList == nil) {
pickList = [[PickFromListViewController alloc] initWithNibName:#"PickList" bundle:nil];
}
TimeLogAppDelegate *appDelegate = (TimeLogAppDelegate *)[[UIApplication sharedApplication] delegate];
NSMutableArray *theList = [[NSMutableArray alloc] init];
int i;
for (i=0;i < [appDelegate.clients count];i++) {
Client *thisClient = [appDelegate.clients objectAtIndex:i];
NSString *clientName = [[NSString alloc] initWithString: thisClient.clientsName];
NSNumber *clientId = [[NSNumber alloc] init];
clientId = [NSNumber numberWithInt:thisClient.clientsId];
NSDictionary *theItem = [NSDictionary dictionaryWithObjectsAndKeys:clientName,#"Name",clientId,#"Id",nil];
[theList addObject:theItem];
theItem = nil;
[clientName release];
[clientId release];
}
[pickList createSearchItems:theList :NSLocalizedString(#"Client",nil)];
[theList release];
appDelegate.returningID = [NSNumber numberWithInt: projectsClientsId];
[self.navigationController pushViewController:pickList animated:YES];
}
Thanks in advance!
This returns allocated NSNumber instance.
NSNumber *clientId = [[NSNumber alloc] init];
This line overwrites the above clientId with another instance of NSNumber, numberWithInt returns autoreleased object, since you haven't allocated memory for it you should not call release, it will be released automatically.
clientId = [NSNumber numberWithInt:thisClient.clientsId];
You are calling release on clientId so you get memory problem.
To fix it remove the first line above which is useless in this case and update the second one to:
NSNumber * clientId = [NSNumber numberWithInt:thisClient.clientsId];
Then remove the:
[clientId release]
Because the clientId will be released automatically.
EDIT: Re still have problems ...
I'm not sure how to you manipulate the clients in app delegate, otherwise the code should work ok, I created small example, omitting the parts that I can't see (app delegate and clients):
// command line utility - foundation tool project:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableArray * theList = [[NSMutableArray alloc] init];
int i = 0;
for (i = 0; i < 10; ++i)
{
NSString * clientName = [NSString stringWithString:#"client"]; //no need to release
NSNumber * clientId = [NSNumber numberWithInt:i];
NSDictionary * theItem = [NSDictionary dictionaryWithObjectsAndKeys:
clientName, #"name",
clientId, #"id",
nil];
[theList addObject:theItem];
}
for (id item in theList) for (id key in item) NSLog(#"%# - %#", key, [item objectForKey:key]);
[theList release];
[pool drain];
return 0;
}
You are creating clientID with [[NSNumber alloc] init], and then immediately overwriting it with an autoreleased NSNumber instance [NSNumber numberWithInt], and then you are releasing it later in your code, which you shouldn't do. Get rid of the [[NSNumber alloc] init] line and the [clientId release] line and that should fix it up a little.
Aside from the obvious leak of the NSNumber, there are a few other things I'd fix that may help. Most are fairly minor, but in my experience with Objective-C, less code == clearer code, something that is not equally true for languages like Bash or Perl. ;-)
- (void) editClient:(id)sender {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (pickList == nil) {
pickList = [[PickFromListViewController alloc] initWithNibName:#"PickList" bundle:nil];
}
TimeLogAppDelegate *appDelegate = (TimeLogAppDelegate*)[[UIApplication sharedApplication] delegate];
NSMutableArray *searchItems = [NSMutableArray array];
NSMutableDictionary *itemDict = [NSMutableDictionary dictionary];
for (Client *client in appDelegate.clients) {
[itemDict setObject:[client.clientsName copy] forKey:#"Name"];
[itemDict setObject:[NSNumber numberWithInt:client.clientsId] forKey:#"Id"];
[searchItems addObject:[[itemDict copy] autorelease]];
}
[pickList createSearchItems:searchItems :NSLocalizedString(#"Client",nil)];
[self.navigationController pushViewController:pickList animated:YES];
appDelegate.returningID = [NSNumber numberWithInt: projectsClientsId];
[pool drain];
}
There are a few mysterious points that make me suspicious:
The line just after the for loop tells pickList to do something with the NSMutableArray. That method should retain the new array, as well as release the old array if one exists. If you just overwrite the pointer, the old array will be leaked. (Also, this method is poorly named. Anonymous arguments (a colon with no preceding text) are legal in Objective-C, but considered extremely bad practice. Consider renaming the method to better express what it does.)
The next line seems to associate the pick list with a navigation controller. If it is custom code, make sure the -pushViewController:animated: method properly releases an existing pick list when a new one is specified.
Assigning to appDelegate.returningID is assumed to call the setter for a returningID property. Be sure that property retains or copies the NSNumber as necessary.
Memory leaks can be tricky to track down, even in Instruments, and you'll often find that it looks like Foundation classes (such as NSDictionary) are leaking like a sieve, but I have always been able to trace it back to an abnormality in my code. :-)