Using the NSPropertyListSerialization
class or Property List Services (Core Foundation), you can serialize property lists in their runtime (object) form to a static representation that can be stored in the file system; later you can deserialize that static representation back into the original property-list objects. Property-list serialization automatically takes account of endianness on different processor architectures—for example, you can correctly read on an Intel-based Macintosh a binary property list created on a PowerPC-based Macintosh.
The property-list serialization APIs allow you to save graphs of property-list objects as binary data as well as XML property lists. See “Property List Representations” for the relative advantages and disadvantages of XML and binary property lists.
The NSPropertyListSerialization
class (available in Mac OS X v10.2 and later) provides methods for saving and restoring property lists from the two major supported formats, XML and binary. To save a property list in XML format, call the dataFromPropertyList:format:errorDescription:
method, specifying NSPropertyListXMLFormat_v1_0
as the second parameter; to save in binary format, specify NSPropertyListBinaryFormat_v1_0
instead.
Listing 5-1 saves an object graph of property-list objects as an XML property list in the application bundle.
Listing 5-1 Saving a property list as an XML property list (Objective-C)
id plist; // Assume this property list exists. |
NSString *path = [[NSBundle mainBundle] pathForResource:@"Data" ofType:@"plist"]; |
NSData *xmlData; |
NSString *error; |
xmlData = [NSPropertyListSerialization dataFromPropertyList:plist |
format:NSPropertyListXMLFormat_v1_0 |
errorDescription:&error]; |
if(xmlData) { |
NSLog(@"No error creating XML data."); |
[xmlData writeToFile:path atomically:YES]; |
} |
else { |
NSLog(error); |
[error release]; |
} |
Note: To avoid a memory leak, it is necessary to release the error-description string returned via indirection in the third parameter of dataFromPropertyList:format:errorDescription:
(as shown in Listing 5-1). This is a rare exception to the standard memory-management rules.
Because you cannot save a property list in the old-style (OpenStep) format, the only valid format parameters for this method are NSPropertyListXMLFormat_v1_0
and NSPropertyListBinaryFormat_v1_0
. The NSData
object returned by dataFromPropertyList:format:errorDescription:
encapsulates the XML or binary data. You can then call the writeToFile:atomically:
or writeToURL:atomically:
method to store the data in the file system.
Note: If the root property-list object is an NSDictionary
or NSArray
object (the typical case), you can serialize the property list as an XML property list and write it out to disk at the same time using the appropriate methods of those classes. See “Reading and Writing Property-List Data” for details.
To restore a property list from a data object by deserializing it, call the propertyListFromData:mutabilityOption:format:errorDescription:
class method of the NSPropertyListSerialization
class, passing in the data object. Listing 5-2 creates an immutable property list from the file at path
:
Listing 5-2 Restoring a property list (Objective-C)
NSString *path = [[NSBundle mainBundle] pathForResource:@"Data" ofType:@"plist"]; |
NSData *plistData = [NSData dataWithContentsOfFile:path]; |
NSString *error; |
NSPropertyListFormat format; |
id plist; |
plist = [NSPropertyListSerialization propertyListFromData:plistData |
mutabilityOption:NSPropertyListImmutable |
format:&format |
errorDescription:&error]; |
if(!plist){ |
NSLog(error); |
[error release]; |
} |
The mutability-option parameter of the deserialization method controls whether the deserialized property-list objects are created as mutable or immutable. You can specify that all objects are immutable, that only the container objects are mutable, or that all objects are mutable. Unless you have a specific reason to mutate the collections and other objects in a deserialized property list, use the immutable option. It is faster and uses less memory.
The last two parameters of propertyListFromData:mutabilityOption:format:errorDescription:
are by-reference. On return from a call, the format parameter holds a constant indicating the on-disk format of the property list: NSPropertyListXMLFormat_v1_0
, NSPropertyListBinaryFormat_v1_0
, or NSPropertyListOpenStepFormat
. You may pass in NULL
if you are not interested in the format.
If the method call returns nil
, the final parameter—the error-description string—states the reason the deserialization did not succeed.
Note: In Mac OS X v10.5 (and earlier versions of the operating system), it is necessary to release the error-description string in Listing 5-2.
Property List Services of Core Foundation has serialization functions corresponding to the class methods of NSPropertyListSerialization
described in “Saving and Restoring a Property List in Objective-C.” To create an XML property list from a property list object, call the CFPropertyListCreateXMLData
function. To restore a property list object from XML data, call the CFPropertyListCreateFromXMLData
function.
Listing 5-3 shows you how to create a complex property list, convert it to XML, write it to disk, and then re-create the original data structure using the saved XML. For more information about using CFDictionary objects see Collections Programming Topics for Core Foundation.
Listing 5-3 Saving and restoring property list data (Core Foundation)
#include <CoreFoundation/CoreFoundation.h> |
#define kNumKids 2 |
#define kNumBytesInPic 10 |
CFDictionaryRef CreateMyDictionary( void ); |
CFPropertyListRef CreateMyPropertyListFromFile( CFURLRef fileURL ); |
void WriteMyPropertyListToFile( CFPropertyListRef propertyList, |
CFURLRef fileURL ); |
int main () { |
CFPropertyListRef propertyList; |
CFURLRef fileURL; |
// Construct a complex dictionary object; |
propertyList = CreateMyDictionary(); |
// Create a URL that specifies the file we will create to |
// hold the XML data. |
fileURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, |
CFSTR("test.txt"), // file path name |
kCFURLPOSIXPathStyle, // interpret as POSIX path |
false ); // is it a directory? |
// Write the property list to the file. |
WriteMyPropertyListToFile( propertyList, fileURL ); |
CFRelease(propertyList); |
// Recreate the property list from the file. |
propertyList = CreateMyPropertyListFromFile( fileURL ); |
// Release any objects to which we have references. |
CFRelease(propertyList); |
CFRelease(fileURL); |
return 0; |
} |
CFDictionaryRef CreateMyDictionary( void ) { |
CFMutableDictionaryRef dict; |
CFNumberRef num; |
CFArrayRef array; |
CFDataRef data; |
int yearOfBirth; |
CFStringRef kidsNames[kNumKids]; |
// Fake data to stand in for a picture of John Doe. |
const unsigned char pic[kNumBytesInPic] = {0x3c, 0x42, 0x81, |
0xa5, 0x81, 0xa5, 0x99, 0x81, 0x42, 0x3c}; |
// Define some data. |
kidsNames[0] = CFSTR("John"); |
kidsNames[1] = CFSTR("Kyra"); |
yearOfBirth = 1965; |
// Create a dictionary that will hold the data. |
dict = CFDictionaryCreateMutable( kCFAllocatorDefault, |
0, |
&kCFTypeDictionaryKeyCallBacks, |
&kCFTypeDictionaryValueCallBacks ); |
// Put the various items into the dictionary. |
// Because the values are retained as they are placed into the |
// dictionary, we can release any allocated objects here. |
CFDictionarySetValue( dict, CFSTR("Name"), CFSTR("John Doe") ); |
CFDictionarySetValue( dict, |
CFSTR("City of Birth"), |
CFSTR("Springfield") ); |
num = CFNumberCreate( kCFAllocatorDefault, |
kCFNumberIntType, |
&yearOfBirth ); |
CFDictionarySetValue( dict, CFSTR("Year Of Birth"), num ); |
CFRelease( num ); |
array = CFArrayCreate( kCFAllocatorDefault, |
(const void **)kidsNames, |
kNumKids, |
&kCFTypeArrayCallBacks ); |
CFDictionarySetValue( dict, CFSTR("Kids Names"), array ); |
CFRelease( array ); |
array = CFArrayCreate( kCFAllocatorDefault, |
NULL, |
0, |
&kCFTypeArrayCallBacks ); |
CFDictionarySetValue( dict, CFSTR("Pets Names"), array ); |
CFRelease( array ); |
data = CFDataCreate( kCFAllocatorDefault, pic, kNumBytesInPic ); |
CFDictionarySetValue( dict, CFSTR("Picture"), data ); |
CFRelease( data ); |
return dict; |
} |
void WriteMyPropertyListToFile( CFPropertyListRef propertyList, |
CFURLRef fileURL ) { |
CFDataRef xmlData; |
Boolean status; |
SInt32 errorCode; |
// Convert the property list into XML data. |
xmlData = CFPropertyListCreateXMLData( kCFAllocatorDefault, propertyList ); |
// Write the XML data to the file. |
status = CFURLWriteDataAndPropertiesToResource ( |
fileURL, // URL to use |
xmlData, // data to write |
NULL, |
&errorCode); |
CFRelease(xmlData); |
} |
CFPropertyListRef CreateMyPropertyListFromFile( CFURLRef fileURL ) { |
CFPropertyListRef propertyList; |
CFStringRef errorString; |
CFDataRef resourceData; |
Boolean status; |
SInt32 errorCode; |
// Read the XML file. |
status = CFURLCreateDataAndPropertiesFromResource( |
kCFAllocatorDefault, |
fileURL, |
&resourceData, // place to put file data |
NULL, |
NULL, |
&errorCode); |
// Reconstitute the dictionary using the XML data. |
propertyList = CFPropertyListCreateFromXMLData( kCFAllocatorDefault, |
resourceData, |
kCFPropertyListImmutable, |
&errorString); |
if (resourceData) { |
CFRelease( resourceData ); |
} else { |
CFRelease( errorString ); |
} |
return propertyList; |
} |
For a discussion of the mutability-option parameter of CFPropertyListCreateFromXMLData
see the discussion of the corresponding parameter of the propertyListFromData:mutabilityOption:format:errorDescription:
method in “Saving and Restoring a Property List in Objective-C.”
Listing 5-4 shows how the contents of xmlData
, created in Listing 5-1, would look if printed to the screen.
Listing 5-4 XML file contents created by the sample program
<?xml version="1.0" encoding="UTF-8"?> |
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" |
"http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
<plist version="1.0"> |
<dict> |
<key>Year Of Birth</key> |
<integer>1965</integer> |
<key>Pets Names</key> |
<array/> |
<key>Picture</key> |
<data> |
PEKBpYGlmYFCPA== |
</data> |
<key>City of Birth</key> |
<string>Springfield</string> |
<key>Name</key> |
<string>John Doe</string> |
<key>Kids Names</key> |
<array> |
<string>John</string> |
<string>Kyra</string> |
</array> |
</dict> |
</plist> |
Cocoa uses the Core Foundation property list API to read and write XML property lists. In some cases, you may wish to access the API directly in an Objective-C Cocoa application. For example, if you want to save an instance of a class other than NSArray
or NSDictionary
as the root object of an XML plist, currently the easiest way to do this is through Property List Services. This process is made simple because Cocoa objects can be cast to and from corresponding Core Foundation types. This conversion between Cocoa and Core Foundation object types is known as toll-free bridging.
To create an XML property list from a property list object, call the CFPropertyListCreateXMLData
function. This code fragment saves the property list plist
into a file at path
:
NSString *path = [NSString stringWithFormat:@"%@/MyData.plist", NSTemporaryDirectory()]; |
id plist; // Assume this is a valid property list. |
NSData *xmlData; |
xmlData = (NSData *)CFPropertyListCreateXMLData(kCFAllocatorDefault, |
(CFPropertyListRef)plist); |
[xmlData writeToFile:path atomically:YES]; |
[xmlData release]; |
To restore a property list object from XML data, call the CFPropertyListCreateFromXMLData
function. This code fragment restores property list from the XML plist file at path
with mutable containers but immutable leaves:
NSString *path = [NSString stringWithFormat:@"%@/MyData.plist", NSTemporaryDirectory()]; |
NSString *errorString; |
NSData *xmlData; |
id plist; |
xmlData = [NSData dataWithContentsOfFile:path]; |
plist = (id)CFPropertyListCreateFromXMLData(kCFAllocatorDefault, |
(CFDataRef)xmlData, kCFPropertyListMutableContainers, |
(CFStringRef *)&errorString); |
Last updated: 2010-03-24