Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to store and retrieve multi-dimensional NSMutableArrays?

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 with
NSMutableDictionary *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.

like image 947
kbanman Avatar asked Jan 26 '10 00:01

kbanman


3 Answers

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.

like image 105
Colin Barrett Avatar answered Oct 23 '22 13:10

Colin Barrett


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];
like image 8
e.James Avatar answered Oct 23 '22 13:10

e.James


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.)

like image 1
Chuck Avatar answered Oct 23 '22 12:10

Chuck