Tuesday, September 29, 2009

Cocoa: NSKeyedArchiver

For a Cocoa application, I want to save an NSColor in a plist file on disk. Saving and loading data like strings and NSNumbers from such a file is easy. Just use writeToFile: atomically (or writeToURL: atomically:), and reload the data with arrayWithContentsOfURL:


- (void)awakeFromNib {
NSMutableArray *mA = [NSMutableArray
arrayWithObjects:@"a",@"b",nil];

NSString *fn = NSHomeDirectory();
fn = [fn stringByAppendingString:@"/Desktop/xyz.plist"];
NSURL *fileURL = [NSURL fileURLWithPath:fn];
[mA writeToURL:fileURL atomically:YES];

NSMutableArray *mA2 = [NSMutableArray
arrayWithContentsOfURL:fileURL];
NSLog(@"%@", mA2);
}


The Console logs the output (some extra stuff has been stripped out):

.. (
a,
b
)


However, if you try the same procedure with an array containing things that can't be saved in this way (like NSColor) the file write method fails silently, with no indication of why it failed. You must do something like this:


- (void)awakeFromNib {
NSMutableArray *mA = [NSMutableArray
arrayWithObjects:@"a",@"b",nil];
NSColor *c = [NSColor blueColor];
[mA addObject:c];

int i;
for (i = 0; i < [mA count]; i++) {
id obj = [mA objectAtIndex:i];
if ([[obj class] isEqual:[c class]]) {
NSLog(@"%@", obj);
id obj2 = [NSKeyedArchiver
archivedDataWithRootObject:c];
[mA replaceObjectAtIndex:i
withObject:obj2];
}
}

NSString *fn = NSHomeDirectory();
fn = [fn stringByAppendingString:@"/Desktop/xyz.plist"];
NSURL *fileURL = [NSURL fileURLWithPath:fn];
[mA writeToURL:fileURL atomically:YES];

NSMutableArray *mA2 = [NSMutableArray
arrayWithContentsOfURL:fileURL];
id obj = [mA2 objectAtIndex:2];
NSColor *c2 = [NSKeyedUnarchiver
unarchiveObjectWithData:obj];
[mA2 replaceObjectAtIndex:2
withObject:c2];
NSLog(@"%@", mA2);
}


.. NSCalibratedRGBColorSpace 0 0 1 1
.. (
a,
b,
"NSCalibratedRGBColorSpace 0 0 1 1"
)


The actual archiving step is a single line. In this case, however, the array is a mixture of different types. For the archiving step, I can test "if ([[obj class] isEqual:[c class]])", but on unarchiving, I think I have to know which objects need to be unarchived. Cocoa knows what they are, but I don't.

There is probably a better way to keep it all straight. Tell me if you know.