2

I have a property list which I read into an NSDictionary, from which I would like to create a mutable copy into an NSMutableDictionary to edit the contents and - later be able to reset the mutable dictionary by copying back the original contents.

NSDictionary *defaultRows;
NSMutableDictionary *rows;

NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"Config" ofType:@"plist"];
defaultRows = [[[NSDictionary alloc] initWithContentsOfFile: plistPath] objectForKey:@"rows"];

This is how I try to create a copy of the original dictionary (according to Apple's recommendation):

rows = [NSMutableDictionary dictionaryWithDictionary: defaultRows];

When I try to change the contents of the dictionary rows with:

[[[rows objectForKey:self.company.coaTypeCode] objectForKey:statementType] removeObjectsAtIndexes:rowsToDelete];

I get the runtime error *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray removeObjectAtIndex:]: mutating method sent to immutable object'.

I have read the article here, but wonder if there is a more elegant method to implement a deep copy?

Community
  • 1
  • 1
AlexR
  • 5,514
  • 9
  • 75
  • 130

2 Answers2

8

Since you are loading defaultRows from a property list, the easiest way to do this is to just deserialize the property list with mutable containers.

NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"Config" ofType:@"plist"];
NSData *rowsData = [NSData dataWithContentsOfFile:plistPath];

NSMutableDictionary *rows = [NSPropertyListSerialization propertyListWithData:rowsData
    options:NSPropertyListMutableContainers format:NULL error:NULL];
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • This a really cool way of doing it! But should the relevant option not be NSPropertyListMutableContainersAndLeaves? I mean, assuming that he wants to edit an array inside an NSMutableDictionary? Otherwise, I think even my answer will work just fine.. :) – Neo Feb 25 '12 at 19:54
  • 1
    The code in his question tries to modify a child container (an array inside a dictionary inside the top-level dictionary), so he needs mutable containers. If he also needs mutable leaves, he can certainly specify `NSPropertyListMutableContainersAndLeaves`. Your answer won't work because it only creates top-level dictionary as mutable; it creates the child containers as immutable. – rob mayoff Feb 25 '12 at 20:02
  • Very good answer, Rob! Thank you! By the way, do you know if the property list reading methods can be configured in a way to read standard XML files into arrays and dictionaries? Currently, I am parsing XML files but would like to read them partially directly into NSDictionaries. – AlexR Feb 25 '12 at 20:24
  • `NSPropertyListSerialization` uses a specific XML schema. It is not a general purpose XML loader. You'll have to keep doing it “the hard way” (presumably using `NSXMLParser`). – rob mayoff Feb 25 '12 at 20:28
  • 1
    Although this serves to help the op - this is not an answer to the question. It assumes the data comes from a Plist file. But if I got this immutable NSDictionary from somewhere else - say NSUserDefaults, or just a property out of a CoreData entity --- what then? I still cannot apply KVC with keyPaths to sub-containers, even if I make a mutableCopy of the original. How to make a deep-mutable-copy? – Motti Shneor Jul 30 '19 at 08:08
  • One of the things I like about Stack Overflow is its focus on practical answers to real problems. As [the site tour says, “Focus on questions about an actual problem you have faced.”](https://stackoverflow.com/tour) In this case, I gave AlexR a practical answer to his actual problem. If you have a real need to deep-copy a plist-type data structure, please post your own question with the details. If it shows up in my feed, I’ll be happy to take a look at it. – rob mayoff Jul 30 '19 at 08:29
1

Did you consider using

NSMutableDictionary *rows = [[[NSMutableDictionary alloc] initWithContentsOfFile:plistPath] objectForKey:@"rows"];
Neo
  • 1,554
  • 2
  • 15
  • 28
  • What would be the performance penalty if the property list would be re-read (instead of creating an in-memory copy)? – AlexR Feb 25 '12 at 19:43
  • 1
    The documentation for `initWithContentsOfFile:` says “The objects contained by this dictionary are immutable, even if the dictionary is mutable.” – rob mayoff Feb 25 '12 at 19:44
  • @AlexR If you keep re-reading stuff, my assumption is it will always be worse than having the object in memory.. Of course, that is not always possible, esp. if you have a HUGE plist. So it might be prudent to create a batch of all such writes and then do it one go, instead of saving them every step of the way. On the other hand, if you are dealing with something like a Settings view, you can always have the object in memory as long as the user is in that particular view. My 2 cents. – Neo Feb 25 '12 at 19:58