Serialization in Cocoa - Property Lists (corrected to text and source code)
Posted 11/18/2008 - 21:59 by cocoacast
Introduction
In the last serialization article, I showed you how to use the archiving capabilities of Cocoa. Cocoa is very powerful, so here goes yet another way to serialize objects: Property Lists. Property lists do not give you a way to specify how and what data is to be serialized. You are forced to use plist-serializable data types: NSArray, NSDictionary, NSData, NSDate, NSNumber, and NSString, their mutable twins, or their Core Foundation equivalents. So, it’s impossible to serialize an arbitrary object like you would with archives, but it is very convenient to store configuration properties, that are usually stored in dictionaries, etc.
There are several types of property lists: XML, binary, and legacy. Legacy property lists store data in a deprecated format going back to NextSTEP days. That format is currently only supported in read-only mode and we will not talk about it. The default plist format is XML: it generates human readable XML files that can get quite large, but are easy to modify and debug. Lastly, the binary plist format is much smaller and efficient and can be used to pass serialized data between multiple applications. Such format is especially useful for communicating with constrained devices, such as iPhone.
Creating Plists from scratch
Plist is an arbitrary structure built of plist-serializable data types. So, in order to create a plist, we simply create an NSDictionary or NSArray that points to other plist-compatible data. Please note that if anything inside of a plist is not compatible, the entire plist will not be serialized! In my example, I build a collection of “War and Peace” volumes like this:
int i;
for (i = 0; i < 3; i++)
{
NSMutableDictionary *book = [NSMutableDictionary new];
[books addObject:book];
NSString *title = [NSString stringWithFormat:@"War and Peace Volume %d", i+1];
[book setObject:@"Leo Tolstoy" forKey:@"Author"];
[book setObject:title forKey:@"Title"];
[book setObject:[NSNumber numberWithInt:3000] forKey:@"Number Available"];
[book setObject:[NSNumber numberWithDouble:0.7] forKey:@"Weight"];
[book setObject:[NSNumber numberWithBool:YES] forKey:@"In Stock"];
NSDate *date = [NSDate date];
[book setObject:date forKey:@"Date Received"];
}
Let’s write out an XML plist file:
- Through NSArray:
- Through serialization API:
- Through Core Foundation:
if (success == NO)
{
NSLog(@"failed saving the XML plist file");
}
NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:books format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
if(xmlData)
{
NSLog(@"No error creating XML data.");
[xmlData writeToFile:@"/Users/vpasman/test1.plist" atomically:YES];
}
else
{
NSLog(error);
[error release];
}
Our options are very similar if we were to save a binary plist, except that we cannot use NSArray’s writeToFile message (it defaults to XML):
- Through serialization API:
NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:books format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error];
if(xmlData)
{
NSLog(@"No error creating XML data.");
[xmlData writeToFile:@"/Users/vpasman/test2.plist" atomically:YES];
}
else
{
NSLog(error);
[error release];
}
Loading and Modifying Plists
For the XML few examples, I will be using a plist file (a.k.a. test.plist in code sample) borrowed from the “Property List Programming Guide”.
Loading plists is very similar to saving, just the opposite. Here is how I will read the XML data:
// in a mutable container! This code will give you warnings, but will compile and run anyway.
// The way to inforce im/mutability is by using serialization API:
// books = [NSPropertyListSerialization propertyListFromData:binData
//mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error];
// This is a wrong way to do this, but it will still work!:
// NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:@"/Users/vpasman/test.plist"];
// Below is the right way to do this:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:@"/Users/vpasman/test.plist"];
NSArray *keys = [dict allKeys];
for (NSString *key in keys)
{
NSLog(@"%@", key);
NSObject *value = [dict objectForKey:key];
if (value != nil)
{
NSString *class = [[value class] description];
NSLog(@"%@", class);
if ([class isEqualTo:@"NSCFNumber"])
{
NSNumber *num = (NSNumber *)value;
NSLog(@"%@", num);
if ([key isEqualToString:@"Birthdate"]) // we know it's a birthdate. this is for example purposes
{
[dict setObject:[NSNumber numberWithDouble:1564.0] forKey:key];
}
}
}
}
NSDate *date = [NSDate date];
[dict setObject:date forKey:@"Date Read"];
Did you notice that I not only was able to read, but also modified my plist data. Now if I save it to the file, it will not fail:
if (success == NO)
{
NSLog(@"failed saving the XML plist file");
}
This points to a major inconsistency in Plist API:
- I just refuted Apple’s claim of how to enforce plist-mutability. According to Apple’s documentation, “NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:@"/Users/vpasman/test.plist"];” creates a mutable dictionary and “NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:@"/Users/vpasman/test.plist"];” creates an immutable dictionary. Those statements are obviousely wishful thinking. Example above will produce warnings but run, not crash, and generate proper output!
We will load a binary plist in a similar fashion to the XML one:
if(binData)
{
NSLog(@"No error creating binary data.");
}
else
{
NSLog(error);
[error release];
}
// If were to use mutabilityOption:NSPropertyListImmutable,
// we would get a run time error when trying to insert another volume
NSPropertyListFormat format;
books = [NSPropertyListSerialization propertyListFromData:binData mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&error];
Workarounds
- Im/Mutability. Despite inconsistencies in Apple’s documentation, there is a way to make a plist immutable in run-time. What this means is that if you were to try to modify an immutable plist in run-time, an exception will be thrown and application will crash. Here is how we will do that:
- If I wanted to create a plist from scratch, the limitation of not being able to store NSNumbers and NSDates may seem severe. Luckily, NSDate and NSNumber instances can be converted to NSString. Thus we’d have to do a bit of extra work, but the constraint does not seem to be very severe.
// we would get a run time error when trying to insert another volume
NSPropertyListFormat format;
books = [NSPropertyListSerialization propertyListFromData:binData
mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error];
Conclusion
Property Lists are very useful for passing information between processes, storing configuration parameters, and much more. It’s a very powerful concept that nicely expands Mac and iPhone OS serialization and configuration capabilities.
Sample Code for this article can be found here.
Vlad





The failure you are getting when serialising NSDate and NSNumber is because in most of your examples you have swapped the object and key parameters when calling setObject as per the example below:
NSDate *date = [NSDate date];
[book setObject:@"Date Received" forKey:date];
I.E. you are using the date value as the key and the name of what should be the the key as the data.
The second line above should read:
[book setObject:date forKey:@"Date Received"];
This will write the following to the plist file:
<key>Date Received</key>
<date>2008-12-14T02:11:45Z</date>
Thanks a lot for the correction. Already appliued it to both the article and source