Inherits From: NSObject
Conforms To: NSObject (NSObject)
Declared In: PPL/PPL.h
The PPL (Persistent Property List) class allows you to incrementally store property lists to and retrieve them from disk. A property list organizes data into named values and lists of values using four classes: NSDictionary, NSString, NSData, and NSArray. A property list is an abstract concept whose implementation can vary depending on context. For example, while PPL stores property lists in a binary format, in other parts of OPENSTEP property lists are represented in an ASCII format. PPL is only one mechanism for creating and storing property lists; for a discussion of other approaches, see the section Alternatives to PPL.
The four classes used in property lists give you the means to produce data that is meaningfully structured, transportable, storable, and accessible, but still as lightweight as possible. You represent basic data types (such as integers and text) with NSString, and binary data with NSData. You use NSDictionary and NSArray to build complex data structures. In an NSDictionary, data is structured as key-value pairs, where the key is a string, and the value can be an NSString, an NSArray, an NSData, or another NSDictionary. In an NSArray, data is structured as a collection of objects that can be accessed by index. An NSArray in an PPL can contain NSStrings, NSDatas, NSDictionaries, and other NSArrays.
For example, this code excerpt creates an PPL, PersonalInfo.ppl
, that stores a person's name, address, and children's names. The example uses combinations of NSString, NSArray, and NSDictionary objects. All data is accessed through a root NSDictionary in which the person's name is an NSString, the children's names are stored in an NSArray, and the address is stored in an NSDictionary containing NSStrings.
PPL *ppl;
NSString *pplPath;
NSMutableDictionary *root;
NSString *name = @"Chris Smith";
NSArray *childArray;
NSMutableDictionary *addressDict;
childArray = [NSArray arrayWithObjects:@"Sam", @"Bettina", @"Eloise", nil];
addressDict = [NSMutableDictionary dictionaryWithCapacity:4];
/* Add data to addressDict. */
[addressDict setObject:@"955 Elm Street" forKey:@"street"];
[addressDict setObject:@"Midland" forKey:@"city"];
[addressDict setObject:@"Kansas" forKey:@"state"];
[addressDict setObject:@"19067" forKey:@"zipcode"];
pplPath = @"PersonalInfo.ppl";
/* Read the PPL; if it doesn't exist, create it. */
ppl = [PPL pplWithPath:pplPath create:YES readOnly:NO];
if (!ppl) {
NSLog(@"Couldn't open or create %@", pplPath);
exit(1);
}
/* Get ppl's root dictionary. */
root = [ppl rootDictionary];
/* Through the root dictionary, add data to ppl. */
[root setObject:name forKey:@"name"];
[root setObject:addressDict forKey:@"address"];
[root setObject:childArray forKey:@"children"];
/* Save ppl. */
[ppl save];
As the above code excerpt illustrates, you access the data in an PPL through its root dictionary. You construct an PPL by adding objects to the root dictionary. How you structure your data within the dictionary is a matter of personal preference. For example, you can set up an NSDictionary of NSDictionaries, an NSDictionary containing NSArrays, and so on. The solution you choose is dependent only on what works best for your data.
When you instantiate an PPL object, a directory is created that has the name you specify. This directory contains two files: store
and log
. store
and log
work in conjunction to ensure that any changes you make to your PPL can be rolled back in the case of a failure. While you're modifying a persistent property list, the changes are recorded in store
and added to log
. log
maintains the property list's old state so that if a failure occurs, the changes made in store
can be rolled back.
Persistent property lists are atomic, meaning that if a save operation fails, the PPL reverts to its previously saved state. An PPL is never left in an intermediate state. Changes to an PPL are applied incrementally (in memory, but not to disk) as you make them and are reflected in the store
and log
files. A save
operation has the effect of committing the changes you've made to disk.
If an PPL becomes damaged (for example, if there's a bad bit on the disk), attempting to access it raises the exception NSInternalInconsistencyException. If this happens, you can retrieve the PPL by catching the exception and using the NSException userInfo
method to get the userInfo NSDictionary. The userInfo NSDictionary usually has the PPL available under the key "PPL", which you can use to rebuild your persistent property list (when possible).
You can store a property list in three different ways: as an ASCII file, in a serialized binary format, and as a persistent property list (PPL). Each of these approaches has its advantages. For example, an ASCII property list is human-readable, but access is slow. Serialization, which stores property lists in a binary format, offers faster access than an ASCII property list and it's also lazy, meaning that you can read parts of files without accessing the whole thing. But serialization doesn't allow you to modify your data and then only re-serialize the part that changed.
Like serialization, a persistent property list stores data in a binary format, provides fast access, and is lazy. It also allows you to make incremental changes to an PPL (even one that contains tens of megabytes of data), while still ensuring that your data is never corrupted. In this sense, an PPL is analogous to a database. Because of their ability to incrementally store and retrieve data, PPLs are particularly well-suited for working with large amounts of data (that is, data that has several elements, that occupies a large number of bytes, or both).
The methods in PPL break down into three general categories:
You use the basic methods to perform most PPL operations, such as creating an PPL, changing its contents, and saving your changes. Basic methods include pplWithPath:create:readOnly:
, initWithPath:create:readOnly:
, rootDictionary
, and save
. These are the methods you use the majority of the time.
The PPLData methods provide read-only snapshots of an entire PPL as an NSData object. This allows you to pass PPLs across processes; passing PPLs as binary data is very fast. Wherever a method has the keyword ...PPLData
(for example, pplWithPath:fromPPLData:readOnly:
), you use the contentsAsData
method to return the contents of an PPL as an NSData object. For example:
PPL *originalPPL; /* Assume this exists. */
NSString *aPath; /* Assume this exists. */
PPL *newPPL = [PPL pplWithPath:aPath
fromPPLData:[originalPPL contentsAsData] readOnly:NO];
The PPLData methods include pplWithPath:fromPPLData:readOnly:
, initWithPath:fromPPLData:readOnly:
, and propertyListWithPPLData:
. These methods provide a fast mechanism for reading and passing read-only copies of an PPL. For example, you can have one process continually updating the contents of an PPL as multiple read-only copies of the PPL are distributed.
The performance tuning methods, flush
, pushChangesToDisk
, detachFromFile
and setCacheHalfLife:
, let you exercise finer-grained control over the behavior of an PPL. You can use flush
and pushChangesToDisk
in conjunction with save
to speed up save operations. detachFromFile
ensures that no more changes are written to disk; this is useful if certain error conditions occur such as running out of disk space or if an PPL becomes damaged. setCacheHalfLife:
lets you fine-tune the trade-offs between memory usage and speed.
pplWithPath:
(NSString *)path create:
(BOOL)createFlag readOnly:
(BOOL)readOnlyFlag
Given a path, reads the PPL stored in it or creates a new one, where readOnlyFlag indicates whether the PPL can be changed, and createFlag indicates whether the PPL should be created if it doesn't already exist. Returns the new PPL object or nil
if the file can't be read.
pplWithPath:
(NSString *)path fromPPLData:
(NSData *)pplData readOnly:
(BOOL)readOnlyFlag
Given a path, reads or creates an PPL that is identical in every way, except for mutability, to the PPL represented by pplData. readOnlyFlag indicates whether the new PPL can be modified; the new PPL doesn't have to have the same mutability or immutability as the PPL specified in the pplData argument. Returns the new PPL object or nil
if the file can't be read.
An PPL is saved when it's created; you don't need to explicitly save it. You can use this method to make local back ups of a remote PPL.
When you use this method or any of the other PPL methods that take NSData arguments, you use contentsAsData
to return a read-only snapshot of an PPL as an NSData object, for example:
PPL *originalPPL; /* Assume this exists. */
NSString *aPath; /* Assume this exists. */
PPL *pplCopy = [PPL pplWithPath:aPath
fromPPLData:[originalPPL contentsAsData] readOnly:NO];
propertyListWithPPLData:
(NSData *)pplData
Returns an immutable root dictionary that gives you access to a snapshot of the PPL specified in the pplData argument.
This method is similar torootDictionary
, in that both methods return a root dictionary object that gives you access to an PPL. However, while rootDictionary
returns an NSMutableDictionary that allows you to modify the associated PPL (so long as the PPL itself is mutable), propertyListWithPPLData:
always returns an immutable NSDictionary whose associated PPL can't be modified. This method is a very fast way of getting a read-only snapshot of an PPL.
contentsAsData
Returns an NSData object containing a read-only snapshot of an entire PPL, that can then be passed to another process. Passing PPLs as binary data is very fast; see the section "Overview of Methods" in the class description for more information.
detachFromFile
Guarantees that no more changes will be written to disk. After this method is invoked, proxies can be released but not accessed. This method is invoked by dealloc
. You can use this method to intervene in certain error conditions: For example, if your PPL becomes damaged, you can use this method to disable writing to disk. You can then delete the damaged PPL and regenerate it (when possible). You should only use this method if you're sure where proxies are when the file is being detached.
flush
Flushes changes to disk. You can use flush
and save
in combination to achieve maximum performance. save
is a costly operation that writes the current state of the PPL to disk and makes the necessary adjustments to the log
and store
files; flush
is faster and it accomplishes much of the work that would otherwise have to be performed by save
. If you use flush
at regular intervals and save only as needed, your save
operations will be faster since flush
does much of the work.
The primary difference between flush
and save
is that if your program fails, your PPL is guaranteed only to revert to the previous save
, not to the previous flush
.
Like save
, flush
guarantees that nothing is done if no change was made to your PPL. All mutable container objects from the PPL that you have not retained are invalid after this method is invoked.
initWithPath:
(NSString *)path create:
(BOOL)createFlag readOnly:
(BOOL)readOnlyFlag
Given a path, reads the PPL stored in it or creates a new one, where readOnlyFlag indicates whether the PPL can be changed, and createFlag indicates whether the PPL should be created if it doesn't already exist. Returns the new PPL or nil
if the file can't be read.
initWithPath:
(NSString *)path fromPPLData:
(NSData *)pplData readOnly:
(BOOL)readOnlyFlag
Given a path, reads or creates an PPL that is identical in every way except for mutability to the PPL represented by pplData. readOnlyFlag indicates whether the new PPL can be modified; the new PPL doesn't have to have the same mutability or immutability as the PPL specified in the pplData argument. Returns nil
if the file can't be read. An PPL is saved when it's created; you don't need to explicitly save it.
pushChangesToDisk
Ensures that the disk image reflects the current state of the PPL. Unlike flush
and save
this method has no effect on non-retained objects, and no notification is posted.
rootDictionary
Returns an PPL's root dictionary, through which you can modify the PPL (so long as the PPL itself is mutable; if it isn't, this method returns an immutable NSDictionary). You interact with an PPL through its root dictionary, which provides access to all of the PPL's other data structures.
Whenever the root dictionary is first modified after a flush
or a save
operation, this method posts the PPLBecameDirty notification with the PPL that was modified to the default notification center.
NSMutableDictionaries returned from this method have the same validity as if the dictionary were in memory, except that they do not survive beyond a save
or flush
operation unless they have been retained or copied.
If you store an NSMutableDictionary, release all references to it, and retrieve it, a proxy is returned rather than the original dictionary. However, if an NSMutableDictionary is stored and retained, you don't get a proxy when you retrieve it-you get the actual object.
save
Saves any changes you made to the PPL to disk and makes the necessary adjustments to the log
and store
files. If save
returns, it means that the changes have been reflected in the PPL. This method guarantees that nothing is done if no change was made. If a save
operation succeeds, this method posts the PPLSaved notification with the PPL that was saved to the default notification center.
You can use save
in combination with flush
to achieve maximum performance. Use flush
at regular intervals and save
as needed. flush
is a faster and less comprehensive operation than save
, and it also makes subsequent save
operations faster since flush
does much of the work that would otherwise have to be performed by save
. The primary difference between flush
and save
is that if your program fails, your PPL is guaranteed only to revert to the previous save
, not to the previous flush
.
As with flush
, all mutable objects that you have not retained are invalid after this operation.
setCacheHalfLife:
(NSTimeInterval)halfLife
Sets the time interval after which cached items are written to disk. Cached items have a 50% chance of being written out after halfLife seconds. If halfLife is zero, cached items are always flushed when the cache is refreshed. If halfLife is less than zero, caching is disabled and can't be re-enabled. This method allows you to fine-tune the trade-offs between memory usage and speed.
Copyright © 1997, Apple Computer, Inc. All rights reserved.