I'm storing a bunch of data in a .plist file (in the application documents folder), and it's structured like this:
Dictionary {
"description" = "String Value",
"sections" = Array (
Array (
Number,
...
Number
),
Array (
Number,
...
Number
)
),
"items" = Array (
Array (
Number,
...
Number
),
Array (
Number,
...
Number
)
)
}
If I just retrieve it withNSMutableDictionary *d = [[NSMutableDictionary alloc] initWithContentsOfFile:plistFile]
I won't be able to replace the number objects, correct?
So I'm recursing through the data right now and forming a mutable version of the whole thing, and it worked in one instance, but now it's telling me mutating method sent to immutable object
when the whole thing is mutable.
Is there an easier/better way to do this? If it makes a difference, my data is just integers and booleans.
Instead of writing all that custom class junk, you should use NSPropertyListSerialization
. Specifically, see the propertyListWithData:options:format:error:
method. Example usage:
NSMutableDictionary *d = [NSPropertyListSerialization propertyListWithData:[NSData dataWithContentsOfFile:@"path/to/file"]
options:NSPropertyListMutableContainers
format:NULL
error:NULL];
This will make all the containers mutable, but keep the leaf nodes (e.g. NSStrings) immutable. There's also an option to make the leaves mutable too.
I usually find it easier to create one or more custom classes to handle loading and saving. This lets you convert the arrays to mutableArrays explicitly:
MyThing.h
@interface MyThing : NSObject
{
NSString * description;
NSMutableArray * sections;
NSMutableArray * items;
}
@property (copy) NSString * description;
@property (readonly) NSMutableArray * sections;
@property (readonly) NSMutableArray * items;
- (void)loadFromFile:(NSString *)path;
- (void)saveToFile:(NSString *)path;
@end
MyThing.m
@implementation MyThing
@synthesize description;
@synthesize sections
@synthesize items;
- (id)init {
if ((self = [super init]) == nil) { return nil; }
sections = [[NSMutableArray alloc] initWithCapacity:0];
items = [[NSMutableArray alloc] initWithCapacity:0];
return self;
}
- (void)dealloc {
[items release];
[sections release];
}
- (void)loadFromFile:(NSString *)path {
NSDictionary * dict = [NSDictionary dictionaryWithContentsOfFile:path];
[self setDescription:[dict objectForKey:@"description"]];
[sections removeAllObjects];
[sections addObjectsFromArray:[dict objectForKey:@"sections"]];
[items removeAllObjects];
[items addObjectsFromArray:[dict objectForKey:@"items"]];
}
- (void)saveToFile:(NSString *)path {
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
description, @"description",
sections, @"sections",
items, @"items",
nil];
[dict writeToFile:path atomically:YES];
}
@end;
With that done, you can encapsulate all of the packaging and unpackaging code in your loadFromFile
and saveToFile
methods. The major benefit of this approach is that your main program gets a lot simpler, and it allows you to access the elements of your data structure as properties:
MyThing * thing = [[MyThing alloc] init];
[thing loadFromFile:@"..."];
...
thing.description = @"new description";
[thing.sections addObject:someObject];
[thing.items removeObjectAtIndex:4];
...
[thing saveToFile:@"..."];
[thing release];
What you want is a deep mutable copy. Cocoa doesn't include a way to do it. A few people have written such deep-copy implementations before (example).
However, Core Foundation includes the CFPropertyList API, which does have support both for creating deep mutable copies of property list objects as well as reading in property lists from disk as mutable datatypes. (And, of course, Core Foundation's property list types are toll-free bridged with Cocoa's, meaning you don't have to convert between them — an NSArray is a CFArray and vice-versa.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With