Property Lists
In Cocoa, there is a class of objects known as property list objects, frequently abbreviated as plist. These lists contain a small set of objects that Cocoa knows how to do stuff with, in particular, how to save them to files and load them back. The property list classes are NSArray, NSDictionary, NSString, NSNumber, NSDate, and NSData, along with their mutable counterparts if they have any.
NSDate
To get the current date and time, use [NSDate date], which gives you an autoreleased object.
NSDate *date = [NSDate date];
NSLog (@"today is %@", date);
NSData
Cocoa provides us the NSData class that wraps a hunk of bytes. You can get the length of the data and a pointer to the start of the bytes.
NSData is an object, the usual memory management behaviors apply. So, if you’re passing a hunk of data to a function or method, you can pass an autoreleased NSData instead without worrying about cleaning it up.
const char *string = "Hi there, this is a C string!";
NSData *data = [NSData dataWithBytes: string
length: strlen(string) + 1]; // C string need to add the trailing 0
NSLog (@"data is %@", data);
// data is <48692074 68657265 2c207468 69732069 73206120 43207374 72696e67 2100>
NSData objects are immutable. Once you create them, that’s it.
Writing and Reading Property Lists
The collection property list classes (NSArray, NSDictionary) have a method called -writeToFile: atomically:, which writes the property lists to files. NSString and NSData also have a writeToFile:atomically: method, but it just writes out strings or blobs of data.
NSArray *phrase;
phrase = [NSArray arrayWithObjects: @"I", @"seem", @"to",
@"be", @"a", @"verb", nil];
[phrase writeToFile: @"/tmp/verbiage.txt" atomically: YES];
Some property list files, especially the preferences files, are stored in a compressed binary format. You can convert these files to something human-readable using the plutil command: plutil -convert xml1 filename.plist.
Encoding Objects
Cocoa has machinery for letting objects convert themselves into a format that can be saved to disk.
Objects can encode their instance variables and other data into a chunk of data, which can be saved to disk. That chunk of data can be read back into memory later, and new objects can be created based on the saved data. This process is calledencodinganddecoding, or serializationand deserialization.
@protocol NSCoding
- (void) encodeWithCoder: (NSCoder *) aCoder;
- (id) initWithCoder: (NSCoder *) aDecoder;
@end
An NSCoder is an abstract class that defines a bunch of useful methods for converting your objects into an NSData and back. You never create a new NSCoder because it doesn’t actually do much. But there are a couple of concrete subclasses of NSCoder that you actually use to encode and decode your objects.
Sample: P296
Notice that there’s a different encodeSomething:forKey: for each type. You need to make sure you use the proper method to encode your types. For any Objective-C object type, you use encodeObject:forKey:
#import <Foundation/Foundation.h>
@interface Thingie : NSObject <NSCoding>
{
NSString *name;
int magicNumber;
float shoeSize;
NSMutableArray *subThingies;
}
@property (copy) NSString *name;
@property int magicNumber;
@property float shoeSize;
@property (retain) NSMutableArray *subThingies;
- (id)initWithName: (NSString *) n
magicNumber: (int) mn
shoeSize: (float) ss;
@end // Thingie
@implementation Thingie
@synthesize name;
@synthesize magicNumber;
@synthesize shoeSize;
@synthesize subThingies;
- (id)initWithName: (NSString *) n
magicNumber: (int) mn
shoeSize: (float) ss
{
if (self = [super init])
{
self.name = n;
self.magicNumber = mn;
self.shoeSize = ss;
self.subThingies = [NSMutableArray array];
}
return (self);
}
- (void) dealloc
{
[name release];
[subThingies release];
[super dealloc];
} // dealloc
- (void) encodeWithCoder: (NSCoder *) coder
{
[coder encodeObject: name
forKey: @"name"];
[coder encodeInt: magicNumber
forKey: @"magicNumber"];
[coder encodeFloat: shoeSize
forKey: @"shoeSize"];
[coder encodeObject: subThingies
forKey: @"subThingies"];
} // encodeWithCoder
- (id) initWithCoder: (NSCoder *) decoder
{
if (self = [super init])
{
self.name = [decoder decodeObjectForKey: @"name"];
self.magicNumber = [decoder decodeIntForKey: @"magicNumber"];
self.shoeSize = [decoder decodeFloatForKey: @"shoeSize"];
self.subThingies = [decoder decodeObjectForKey: @"subThingies"];
}
return (self);
} // initWithCoder
- (NSString *) description
{
NSString *description =
[NSString stringWithFormat: @"%@: %d/%.1f %@",
name, magicNumber, shoeSize, subThingies];
return (description);
} // description
@end // Thingie
int main (int argc, const char * argv[])
{
@autoreleasepool
{
Thingie *thing1;
thing1 = [[Thingie alloc]
initWithName: @"thing1"
magicNumber: 42
shoeSize: 10.5];
NSLog (@"some thing: %@", thing1);
NSData *freezeDried;
freezeDried = [NSKeyedArchiver archivedDataWithRootObject: thing1];
[thing1 release];
thing1 = [NSKeyedUnarchiver unarchiveObjectWithData: freezeDried];
NSLog (@"reconstituted thing: %@", thing1);
Thingie *anotherThing;
anotherThing = [[[Thingie alloc]
initWithName: @"thing2"
magicNumber: 23
shoeSize: 13.0] autorelease];
[thing1.subThingies addObject: anotherThing];
anotherThing = [[[Thingie alloc]
initWithName: @"thing3"
magicNumber: 17
shoeSize: 9.0] autorelease];
[thing1.subThingies addObject: anotherThing];
NSLog (@"thing with things: %@", thing1);
freezeDried = [NSKeyedArchiver archivedDataWithRootObject: thing1];
thing1 = [NSKeyedUnarchiver unarchiveObjectWithData: freezeDried];
NSLog (@"reconstituted multithing: %@", thing1);
[thing1.subThingies addObject: thing1];
// You really don't want to do this...
// NSLog (@"infinite thinging: %@", thing1);
freezeDried = [NSKeyedArchiver archivedDataWithRootObject: thing1];
thing1 = [NSKeyedUnarchiver unarchiveObjectWithData: freezeDried];
}
return (0);
} // main