How to make a deep copy of an NSDictionary, the easy way? - iphone

How to make a deep copy of an NSDictionary, the easy way? In particular, it's an NSUserDefaults dictionary that contains only property list objects, so objects which are serializable.
Would I just iterate over it and build a complete new one with copied values? Guess you guys have a better solution.

You could use
newDict = [[NSDictionary alloc] initWithDictionary:oldDict copyItems:YES];

Related

Position of the key "__type" in a JSON WCF service being consumed on iPhone

I'm trying to consume a webservice made by my client on the iPhone. It was used, from what they told me, using WCF. The problem is that I need to send a bunch of parameters to a method, and one of them is a key called "__type", that needs to be the first key on the dictionary. I tried to argue that Dictionaries, by definition, do not guarantee the order of the keys, but the insist that it is necessary that this specific key is the first in the dictionary.
From what I gathered, this property is what they call a type hint, and it really has to be the first property on the dictionary.
Is there any way for me to do this on the iPhone?
Perhaps a little late, but I would recommend SBJson framework for this.
By setting the sortKeys property on SBJsonwriter, your keys will be sorted alphabetically.
Presumably your keys have names that will make __type the first key in the sorted JSON.
NSDictionary *unsortedDictionary = [[NSDictionary alloc]
initWithObjectsAndKeys:
#"Doe", #"lastName",
#"Person", #"__type",
#"John",#"firstName",
nil];
SBJsonWriter *writer = [[SBJsonWriter alloc] init];
writer.sortKeys = YES;
NSLog(#"%#",[writer stringWithObject:unsortedDictionary]);
This will produce nicely sorted JSON:
{"__type": "Person","firstName": "John","lastName": "Doe"}

Removing elements in NSMutableDictionary is deleting other dictionary

I'll try to be as much clear as possible:
I create 2 MutableDictionary
I add to both of them the same NSMutableArray object:
[self.myList setObject:tempC forKey:keyV];
[self.listFiltered setObject:tempC forKey:keyV];
In other part of the code, I want to empty one, so I do:
[self.listFiltered objectForKey:keyV] removeAllObjects];
The problem is that the objects are being removed in BOTH mutableDictionaries!
The same NSNSMutableArray is added to both dictionaries. It doesn't matter whether you access it through one dictionary or the other, you end up manipulating the same NSMutableArray instance.
To fix this, you need store different NSMutableArray instances in each dictionary. The easiest way to do this is through [NSMutableArray copy], which will do a shallow copy.
Lastly, naming dictionaries with names ending in list is a bad practice.
You need to copy the array to the other dictionary.
See here on how you can go about this.
Both objects are same... you only provide a pointer to that mutablearray to both the dictionary so when you delete in one the other also gets deleted .. what you need is to store copy of that array into your dictionary... hoping this helps.
you can add the array like this..(Seems to me you want a deep copy)
NSMutableArray *newArrayOne = [[NSMutableArray alloc] initWithArray:tempC copyItems:YES];
[self.myList setObject:newArrayOne forKey:keyV];
[newArrayOne release];
NSMutableArray *newArrayTwo = [[NSMutableArray alloc] initWithArray:tempC copyItems:YES];
[self.listFiltered setObject:newArrayTwo forKey:keyV];
[newArrayTwo release];
this way two different objects are stored in there.. this is not the most optimized code.. it is just to make you understand what actually is happening behind the scenes.

Accessing array/set of objects with keys

I want to be able to add objects to an NSArray and access them with Keys. Currently the way im doing it is creating a seperate NSDictionary of key-value pairs where the value is an integer number representing the index in my NSArray. This seems like an extra step to me.
If my understanding of NSDictionary is correct, only 'values' can be stored: a pointer to an object cannot.
Surely there must be an equivalent NSDictionary type function that allows objects to be stored and accessed with a key? I have looked through the documentation, but cant seem to find any answers, unless im missing something obvious.
NSDictionary is to store key value pairs. if you are adding key value pair after you created the dictioanry, use NSMutableDictionary class . example,
[dictionaryObject setObject:#"" forKey:#"abc"];
You can store objects in NSDictionary and can be accessed via keys...
In short, no.
An array (NSArray) is an ordered collection of references to objects, so simply said, an ordered collection of objects.
As opposed to dictionaries, which are unordered and values are accessed by keys.
You understanding of collections is probably wrong, you don't store values, but pointers (references).
The extra step is necessary if you need to store the references in an array, but in this case, you should consider using a dictionary. An option is to use keys that take care of the order.
For example :
[myDictionary objectForKey:#"1"];
could be an equivalent of :
[myArray objectAtIndex:1];
Thats wrong, you can store objects in a NSDictionary. Look at the method dictionaryWithObjects:forKeys: or dictionaryWithObjectsAndKeys:
I have no experience in Cocoa but looking at the documentation it seems like NSDictionary (or at least NSMutableDictionary) should handle your request (without you using NSArray).
I think I understand your problem. My suggestion for you is to use NSMutableArray and macros, like:
NSMutableArray *array=[[NSMutableArray alloc]init];
#define SOME_MACRO 0
id someObject;
[array insertObject:someObject atIndex:SOME_MACRO];
id getterObject=[array objectAtIndex:SOME_MACRO];
Of course define the macros in the header file.

Do I need to initialize an iOS empty nested array that's part of a plist import?

the code below is working, but I want to make sure it's correct. I'm nervous about having an empty Array inside my dictionary that I create from the plist, since typically it seems that if you don't, say, initWithCapacity:1 then you often get memory errors once you start trying to add items.
At least, that's been my experience with NSMutableDictionary. However, this is the first time I'm trying to implement nested data objects, so perhaps the reason this code works is that the nested array is automatically initialized when it's imported as part of its parent dictionary?
Any and all comments appreciated. Thanks.
First, here's what the plist looks like that I'm using to create my dictionary:
Next, here's my code where I'm using the plist to create a dictionary, then adding an item to dataArray
// Create a pointer to a dictionary
NSMutableDictionary *dictionary;
// Read "SomeData.plist" from application bundle
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"SomeData.plist"];
dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:finalPath];
// Now let's see if we can successfully add an item to the end of this empty nested array. How 'bout the number 23
NSNumber *yetAnotherNumber = [NSNumber numberWithInt:23];
[[dictionary objectForKey:#"dataArray"] addObject:yetAnotherNumber];
// Dump the contents of the dictionary to the console
NSLog(#"%#", dictionary);
Okay, fine, simple, good. When I Log the dictionary contents it shows that "23" has been added as an array value to dataArray. So the code works. But again, I want to confirm that I'm not "getting lucky" here, with my code just happening to work even though I'm not properly initializing that nested array. If so, then I could run into unanticipated errors later on.
So to sum up, dataArray is an empty array inside the .plist, so do I need to initialize it somehow (using, for example initWithCapacity: or something else) before I can properly populate it, or is the way I'm coding here just fine?
Thanks again.
EDIT
Hey all. I've been doing continued research on this, in the interests of finding a satisfying answer. I think I may have stumbled upon something, via this link on deep copying. His previous posts on deep copying had presented some code to do essentially what I was looking for above: create a mutable copy of a dictionary or array, from a plist, that also has mutable sub-structures.
However, as mentioned in the link above, it looks like these methods were superfluous, due to the CFPropertyListCreateDeepCopy method, which can be invoked with a call such as
testData = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, [NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);
So, my question is, can I properly use CFPropertyListCreateDeepCopy, in the way shown, to achieve what I've been asking about here? In other words, can I use this method to import my dictionary from a plist with fully mutable, nested data objects?
As I mentioned in one of the comments, I know I can create a nested, mutable dictionary manually, but for complex data that's just not practical, and it seems unlikely that built-in methods to import a mutable plist don't exist. So, based on the above, it looks like I've possibly found the solution, but I'm still too new to this to be able to say for sure. Please advise.
(Side note: I would simply test the code, but as we've established, the current SDK is buggy with regard to allow you to edit immutable nested dictionaries, contrary to the documented behavior. So as before, I'm not just interested in whether this works, but whether it's correct)
Thanks in advance.
init... methods should only be called once, immediately after a call to alloc or allocWithZone:. When framework code creates and returns an object or graph of objects, their init... methods have already been called, so sending another init... message would have undefined results. Don't do that.
Interestingly, in spite of what the documentation appears to say (and admittedly I probably missed a key sentence or paragraph somewhere), when you create an instance of a mutable collection by reading a plist, any nested collections are also mutable. I ran the following little experiment in a test harness just to be sure:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSMutableArray *cats = [pets objectForKey:#"cats"];
[cats addObject:#"Foo"]; // EDIT: Added line I accidentally omitted earlier
NSLog(#"%#", cats);
So again, the nested collections created when you read in the plist are fully initialized, and mutable to boot, so you can simply use them, as you've been doing.
EDIT
However, after doing some further reading of the docs, I think the OP is right to feel uneasy about relying on what is apparently an undocumented feature of the current version of the SDK. For example, the Property List Programming Guide states:
If you load the property list with
this call:
NSMutableArray * ma = [NSMutableArray arrayWithContentsOfFile:xmlFile];
ma is a mutable array with immutable
dictionaries in each element. Each key
and each value in each dictionary are
immutable.
So, to be on the safe side, if you need a nested collection to be mutable, you should create it yourself. For example, I'd recommend rewriting the code in the example above as follows:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSArray *cats = [pets objectForKey:#"cats"];
NSMutableArray *mutableCats = [cats mutableCopy];
[pets setObject:mutableCats forKey:cats];
[mutableCats release];
You can then safely make changes to the nested mutable collection:
[mutableCats addObject:#"Foo"];
Any object in a dictionary which is created by reading from disk will be properly initialized. You will not have to do it on your own. However, as pointed out by jlehr, contents of the dictionary should be immutable. If you want the contents of the dictionary to be mutable, you will need to change them on your own. I have no idea why your program is not throwing an exception.
I do not know why you are getting memory errors while not using initWithCapacity:1 in other situations. The following code is perfectly valid:
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:#"object1"];
[array addObject:#"object2"];
NSLog(#"%#",array);
[array release];
If you don't specify a capacity, the array won't pre-allocate any memory, but it will allocate memory as required later.
Edit:
It is perfectly acceptable to use NSDictionary with CFPropertyListCreateDeepCopy. In Core Foundation, a CFPropertyList can be a CFDictionary, CFArray, CFNumber, CFString, or CFData. Since NSDictionary is toll-free bridged to CFDictionary, you can use it wherever a CFDictionary is asked for by casting, and vice-versa. Your code as is will give a warning, but you can suppress it by casting the dictionary and return values.
NSDictionary *testData = (NSDictionary*)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)[NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);

Copying the contents of an NSMutableDictionary into another NSMutableDictionary?

I have an NSMutableDictionary each element of which is another dictionary. What is the best way I can copy its contents into another NSMutableDictionary? I've tried using:
firstDictionary = [NSMutableDictionary dictionaryWithDictionary:secondDictionary];
However not sure if this is the best way to do it.
You can also jump between mutable and non-mutable dictionaries using copy and mutableCopy.
- (void) someFunc:(NSMutableDictionary *)myDict {
NSDictionary *anotherDict = [myDict copy];
NSMutableDictionary *yetAnotherDict = [anotherDict mutableCopy];
}
Check the NSDictionary initWithDictionary:copyItems: method.
It it enables deep copying of elements thru calling copyWithZone: method of item's class. You will have to take care of copying the fields yourself within the method.
What do you mean by "best"?
Anyway, I listed some ways here:
firstDictionary =
[NSMutableDictionary
dictionaryWithDictionary:secondDictionary];
[[NSDictionary alloc]
initWithDictionary:secondDictionary];
//don't forget to release later
using deep copy
using shallow copy
Conform to NSCopying Protocol and do copyWithZone on every object.
If NsMutableDictionary contains another dictionary, which contains another dictionary,, then you need to do copyWithZone on each dictionary at all levels.