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.
Related
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.
I would like to save the content of a NSMutableDictionary object to a file. How do I do this ? I already know how to do this task with a NSDictionary object but I don't know how to convert/copy this NSMutableDictionary to a NSDictionary...unless there's a method to write directly the content of NSMutableDictionary to a file...I stress that the NSMutableDictionary object contains objects of NSDictionary type.
Thx for helping,
Stephane
NSMutableDictionary is a subclass of NSDictionary: you can use it anywhere you'd use NSDictionary. Literally, just pass the object through to the same code you use for NSDictionary right now.
In a more general sense, if you ever actually need to get a truly immutable NSDictionary from an NSMutableDictionary, just call copy on the NSMutableDictionary.
Other approaches include [NSDictionary dictionaryWithDictionary:] or [NSDictionary alloc] initWithDictionary:], which all amount to essentially the same thing.
If you just swing over to the NSDictionary documents. You will see there is a method for saving a dictionary to a file
writeToFile:atomically:
Writes a property list representation of the contents of the dictionary to a given path.
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
Parameters
path:
The path at which to write the file.
If path contains a tilde (~) character, you must expand it with stringByExpandingTildeInPath before invoking this method.
flag:
A flag that specifies whether the file should be written atomically.
If flag is YES, the dictionary is written to an auxiliary file, and then the auxiliary file is renamed to path. If flag is NO, the dictionary is written directly to path. The YES option guarantees that path, if it exists at all, won’t be corrupted even if the system should crash during writing.
Return Value
YES if the file is written successfully, otherwise NO.
This method recursively validates that all the contained objects are property list objects (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary) before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
If the dictionary’s contents are all property list objects, the file written by this method can be used to initialize a new dictionary with the class method dictionaryWithContentsOfFile: or the instance method initWithContentsOfFile:.
So the piece of code you are looking for is probably something like:
[myDict writeToFile:path atomically:YES]
where myDict is the dictionary you have and path is the path to the location you want to save it to
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);
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];
I'm encountering an extremely vexing problem. I have a UITableViewController which in its init method decodes a dictionary from a JSON or plist file (I've tried both), then retrieves an array from that dictionary. Later on, in the method tableView:numberOfRowsInSection:, I'm returning the count of that array.
However, for reasons beyond me, calling count on the array at that point crashes the application, though calling count directly after assignment in init doesn't. Also, if I replace the initial assignment with a programmatically created array (via NSArray initWithObjects), it works fine.
JSON decoding in init:
NSString *jsonPath = [[NSBundle mainBundle] pathForResource:#"Categories" ofType:#"json"];
SBJSON *jsonParser = [SBJSON new];
NSDictionary* dict = [jsonParser objectWithString:[NSString stringWithContentsOfFile:jsonPath encoding:NSUTF8StringEncoding error:nil]];
categories = [dict objectForKey:#"ContentCategories"];
// Outputs correct count
NSLog(#"Count: %#", [NSNumber numberWithInt:[categories count]]);
Programmatic init:
categories = [[NSArray alloc] initWithObjects: [[NSDictionary alloc] initWithObjectsAndKeys:#"Junk", #"Title"]];
// Outputs correct count
NSLog(#"Count: %#", [NSNumber numberWithInt:[categories count]]);
UITableViewController number of rows method:
// Outputs correctly if programmatically created, crashes if decoded from JSON/plist
NSLog(#"Count: %#", [NSNumber numberWithInt:[categories count]]);
I've tried the "categories" variable as an ivar, a propertied-ivar, and as a class variable, with no luck.
Thanks for your help!
I'm not familiar with that JSON parser, but if it's following Cocoa conventions, objectWithString will be returning an autoreleased object. This means, it will be deallocated on the next iteration through the runloop, unless you retain it. When Cocoa collection classes deallocate, they release each object they contained, so categories will also get released. This is why it works at first, but not later in the program.
The reason it works by making it a property, is that the synthesized setter created retains categories (assuming you set your property with the retain parameter, which I assume you did), so it won't get released. That is the proper solution, nice work. :) Calling retain on categories would also have worked.
The Cocoa convention is that if a method name contains "alloc" or "copy", or begins with "new", the object returned has a retain count of 1, and you are responsible for releasing it. Otherwise, the returned object has a retain count of zero (it has been autoreleased), and will be deallocated on the next iteration through the runloop, unless you retain it (in which case you take responsibility for releasing it later).
I recommend reading this:
Memory Management Programming Guide for Cocoa
Edit:
The reason it worked using [[NSArray alloc] initWithObjects...] is, because that method contains "alloc", the returned array has not been autoreleased, and so is still around later when you access it.
Note: changing "categories" to a property and adding "self." in front of all of the setting/getting methods (e.g., self.categories = ...) made this work. For some reason, calling just "categories" was returning an object of type UIWindow in the numberOfRows method (!). That seems incredibly odd to me (I would hope I would just encounter compiler errors), but then again, I'm relatively new to obj-c.