Mutable deep-copy of a NSMutableDictionary - copy

First of all I found something similar:
deep mutable copy of a NSMutableDictionary
but it didn't solve my problem.
I have a NSMutableDictionary as a template.
NSMutableDictionary *mutableDictionaryTemplate = [NSMutableDictionary dictionaryWithObjectsAndKeys: #"obj1, #"key1",#"obj2, #"key2",nil];
Now I would like to copy this dictionary, then change some parts and later save it in a NSMutable Array.
NSMutableArray *savedIterations = [NSMutableArray new];
//some loop that normally would change the objects added
int i=0;
for (i < 5){
NSMutableDictionary *copiedDictionary = [mutableDictionaryTemplate copy];
[copiedDictionary setObject:#"obj3" forKey:#"key3"];
[savedIterations addObject:copiedDictionary];
i++;
}
My problem is that once I copy the NSMutableDictionary "mutableDictionaryTemplate" it no longer is mutable. But I need to copy it because otherwise I will have the same NSMutableDictionary at every index of my NSMutableArray "savedIterations" (at least I think so). I tried mutable copy as well but there I change the "mutableDictionaryTemplate" when I change "copiedDictionary". I think I have something messed up with what I have to copy and what not and how to copy it correctly.
It would be great if someone could point me into the right direction.

You could try this:
NSMutableDictionary *destinationDictionary = (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (__bridge CFPropertyListRef)(sourceDictionary), kCFPropertyListMutableContainers));

Related

Can't update an NSMutableDictionary value from within a for-in loop?

I'm having a weird issue with updating an NSMutableDictionary value. I'm running a for-in loop, everything is retrieved fine, and my math is fine.
The problem lies when I try to update my dictionary with the setValue: forKey: method.
for(NSString *key in self.planetDictionary){
if(![key isEqualToString:planet]){
* * *
//do some math and stuff, create an NSNumber:
NSNumber *update = [NSNumber numberWithFloat:updatedProbability];
//Problem code, EXC_BAD_ACCESS here:
[self.planetDictionary setValue:update forKey:key];
}
}
I get EXC_BAD_ACCESS crashes. I can confirm everything else is fine, its just the single line where I try to update the value.
What's going on? Thanks
You're not allowed to mutate an object that you're fast enumerating. It will crash every time. As a work around, you can make a copy first:
NSDictionary *dictionary = [self.planetDictionary copy];
for (NSString *key in dictionary) {
if (![key isEqualToString:planet]) {
NSNumber *update = [NSNumber numberWithFloat:updatedProbability];
[self.planetDictionary setValue:update forKey:key];
}
}
Alternatively, if you have large objects in your dictionary (that conform to NSCopying, or else it would just be a shallow copy and won't matter very much), and hence you don't want to copy all the objects, then you could simply copy the keys (as an array) and enumerate them like this:
NSArray *keysCopy = [[self.planetDictionary allKeys] copy];
for (NSString *key in keysCopy) {
if (![key isEqualToString:planet]) {
NSNumber *update = [NSNumber numberWithFloat:updatedProbability];
[self.planetDictionary setValue:update forKey:key];
}
}
Looks like you're mutating it while enumerating it.
You can't change the NSDictionary while you're looping through it.
You might want to make a copy of it before the loop and enumerate the copy while you change the original.
You cannot update it while using it in for loop. Use a copy of the dictionary keys to use in for loop and update the original inside the for loop.

Setting Values in NSArray

Looked around for a while and couldn't find an answer to this, but it seems like a pretty simple thing. I want to create an NSArray that has room for 100 objects (Doors) and then just loop through to create a new door for each index in the array. I couldn't find a way to do this without manually writing the code for 100 new doors when I init the NSArray. I know I could do this by just creating an NSMutableArray and using addObject but I've heard NSArrays are much faster and I'd like to know how to do this for future reference.
Here's what I'm basically trying to do:
NSArray *doors = [[NSArray alloc]init]; //with size 100?
for (int i = 0; i < [doors count]; i++)
[[doors objectAtIndex:i] = [[Door alloc]init]];
if you are going to add objects to an array inside of a loop, then NSMutableArray is the correct object to use.
To create 100 doors:
NSMutableArray *doors = [NSMutableArray new];
for (int i=0; i < 100; i++){
[doors addObject:[[Door alloc]init]];
}
Now you have an mutable array with 100 Door Objects.
If you have no need to later modify the array, you can convert it back to
an immutable array for processing like this:
NSArray *newDoorList = [NSArray arrayWithArray:doors];
There may be some minor performance hits between mutable and non mutable arrays, but you will not notice them with just a few hundred objects - at least that has been my experience.
hope that helps. good luck.
Create an NSMutableArray and then use [array copy] to get a non-mutable NSArray.
See this answer for some options, basically create an NSMutable array and use that to create your NSArray...
http://www.cocoabuilder.com/archive/cocoa/190315-nsmutablearray-to-nsarray.html#190321
NSMutableArray is a dynamic array, and you don't need to predefine a size. As Aleph suggests, use an NSMutableArray and append your door object 100x. Like this:
NSMutableArray *myArray = [NSMutableArray arrayWithCapacity:100];
for ( int i = 0; i < 100; i++ ) {
[myArray addObject: [[Door alloc] init]];
}
NSArray *filledArray = [NSArray arrayWithArray:myArray];
Just edited to include the arrayWithCapacity: method. Per this SO thread, if you know the exact number you need, it should just allocate the memory all at once. Besides that, NSArray vs. NSMutableArray isn't going to show that much difference speed-wise. ( Until proven wrong. O_o )
Disadvantage of using NSMutableArray vs NSArray?

NSDictionary Memory leaks

I implement a method to return a dictionary in my app. But I find a memory leak using instrument, I tried to figure it out, but I still cannot find it. Can anyone help me out?
Thanks in advance, here is the code for that methods:
-(NSMutableDictionary *)initDict
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
[dict setObject:self.name forKey:#"Name"];
//Some similar set object for key here...
return dict;
}
I think the problem is from allocing memory for dict and not releasing it. But in the method, it seems I cannot release dict. So is there any method to fix the leak?
All variants are good. Here is third variant (choose wisely):
Replace
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
with
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
I think you just need to change the last line of initDict to this:
return [dict autorelease];
When you are creating any object in your function and you want to return them then you should always create it in a way that those objects are autoreleased. So you should change your code like below.
NSMutableDictionary *dict = [[[NSMutableDictionary alloc]init]autorelease];
So whenever you create any object just call autorelease method on that.

Creating Arrays from a plist file in iphone SDK 3.3 and up : objective c

I have run into this issue and have put some major time into finding the answer. I am somewhat new to objective c but not to programming.
Here is my question.
I have a plist file with this structure
root {
A (
{songTitle : contents of song},
{songTitle : contents of song}
),
B (
{songTitle : contents of song}
),
C (
{songTitle : contents of song}
),
... kepps going
}
Sorry if the the plist structure is not correct.
Pretty much I have a root dictionary (that is what it comes with) that contains an array of A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,...Z (alphabet)
Each letter of the alphabet array contains 1 or more dictionaries that have a key, value pair of songTitle (this could be any string) as the key and the song lyrics for the value.
My issue here is I need to create an array of all song titles and have been having a rough time trying to find out how to do this. I own 4 books on object c and none of them go into detail about multidimensional arrays and how to access pieces inside them.
I have created an array with all the letters and have created an array that contains the objects from each letter.
Like I stated before I need to find out how to make an array that contains each song title.
If you can help me that would save me a lot of time.
Thanks,
Wes
I am guessing you are suggesting I change my root from a dictionary to an array?
Maybe it might be better to show my code here.
Also I have attached an updated version of my plist file
Sorry seems I cannot add the image here but you can view it
http://www.wesduff.com/images/forum_images/plist_examp.png
So as you can see I have updated the plist file to show the array of letters that each contain multiple dictionaries. Each dictionary has a songTitle and a songLyrics.
How can I write code to get an array of songTitles.
Here is what I have come up with so far
NSString *path = [[NSBundle mainBundle] pathForResource:#"songs" ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
//This gives me an array of all the letters in alphabetical order
NSArray *array = [[dict allKeys] sortedArrayUsingSelector:#selector(compare:)];
/**
Now I need to find out how to get an array of all songTitles
**/
I am still working on this and looking through what others have written but have not found anything yet.
As the first answer has suggested, should I change the root to an array or keep it as I have it in this plist image I have attached.
Thanks again,
Wes
Ok so I did some more digging and came up with this from the plist file that was included in this picture
http://www.wesduff.com/images/forum_images/plist_examp.png
- (void)viewDidLoad {
//path for plist file
NSString *path = [[NSBundle mainBundle] pathForResource:#"songList" ofType:#"plist"];
//dictionary created from plist file
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
//release the path because it is no longer needed
[path release];
//temp array to hold an array of all alphabetical letters
NSArray *array = [[dict allKeys] sortedArrayUsingSelector:#selector(compare:)];
//assign array to allLetters array
self.allLetters = array;
//Create two mutable arrays for the songArray (could do a little cleaner job of this here)
NSMutableArray *songArray = [[NSMutableArray alloc] init];
NSMutableArray *songTitles = [[NSMutableArray alloc] init];
//Add messy array to songArray then we can work with the songArray (maybe something better to do here)
for(id key in dict)
{
[songArray addObject:[dict objectForKey:key]];
}
//temp array to hold a messy array for all of the songTitles
NSArray *tempArray = [songArray valueForKey:#"songTitle"];
//go through the temparray and clean it up to make one array of all song titles and sort them
for (NSUInteger i = 0; i < [tempArray count]; i++) {
[songTitles addObjectsFromArray:[[tempArray objectAtIndex:i] sortedArrayUsingSelector:#selector(compare:)]];
}
//assign all song titles to our array of songTitles
self.allSongTitles = songTitles;
[dict release];
[allSongTitles release];
[songArray release];
[tempArray release];
[array release];
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
I am sure there is probably a better way to do this but this is what I have come up with on my own. Thanks
If you have single array with the contents of all the letters, the rest is fairly simple. Iterate through the objects and call the dictionary method allKeys on each one. Each call to allKeys will return an NSArray containing the keys of that specific dictionary, which you can then place into another array.
EDIT
I made a mistake, didn't go deep enough. This is what I would do:
NSString *path = [[NSBundle mainBundle] pathForResource:#"songs" ofType:#"plist"];
NSDictionary plistDict = [NSDictionary dictionaryWithContentsOfFile:path]; //not using alloc and init means this isn't retained, so it will be autoreleased at the end of the method
NSArray *allLetterContents = [plistDict allValues]; // array of arrays, where each element is the content of a 'letter' in your plist (i.e. each element is an array of dictionaries)
NSMutableArray *allSongTitles = [[[NSMutableArray alloc] init] autorelease];
for(NSArray *oneLetterContents in allLetterContents)
{
for(NSDictionary *song in oneLetterContents)
{
[allSongTitles addObject:[song objectForKey:#"songTitle"]]
}
}
return allSongTitles;
This array isn't guaranteed to be sorted alphabetically, so you'll have to call [sortedArrayUsingSelector:] on it.
Reference:
NSMutableArray Class Reference
NSDictionary Class Reference

Mutable Object within an Immutable object

Is it acceptable to have a NSMutableArray within an NSDictionary? Or does the NSDictionary also have to be mutable?
The NSMutableArray will have values added to it at runtime, the NSDictionary will always have the same 2 NSMutableArrays.
Thanks,
Dan
Yes, it's perfectly acceptable. Keep in mind, the contents of the array are the pointers to your NSMutableArrays--those are what can't change in the immutable dictionary structure. What the pointers point to can change all you want. To wit:
NSMutableArray *arr = [[NSMutableArray alloc] init];
NSDictionary *dict = [NSDictionary dictionaryWithObject:arr forKey:#"test"];
[arr addObject:#"Hello"];
NSString *str = [[dict objectForKey:#"test"] objectAtIndex:0];
NSLog("%#", str);
It's quite acceptable. But, it's precisely the sort of setup that suggests you should seriously consider replacing the dictionary with an NSObject subclass that sports two properties for accessing the arrays.