4.3 - Load and Save#
As a document-based application, Money.app has to be able to load and save files. Otherwise, the user will lose their work when they quit the application.
Depending on the structure of the files, I have several choices to
override in NSDocument
:
Table 4-1. Methods for loading and saving files
Load file | Save file | |
Single file | loadDataRepresentation:ofType: | dataRepresentationOfType: |
Bundle | loadFileWrapperRepresentation:ofType: | fileWrapperRepresentationOfType: |
Low level | readFromFile:ofType:, readFromURL:ofType: | writeToFile:ofType:, writeToURL:ofType: |
Read How do I implement saving and loading for simple files?, How do I implement document packages (documents that are really folders, but appear to be opaque documents)?, and How do I implement loading and saving when the simple data or file wrapper API won’t do?. (Don’t open all the links at once – they’re all on the same FAQ page.)
In this case, -loadDataRepresentation:ofType:
and
-dataRepresentationOfType:
are enough.
Document.m:
- (NSData*) dataRepresentationOfType: (NSString*) type
{
if (type == nil) {
type = @"mon";
}
if ([type isEqualToString: @"mon"]) {
return [NSArchiver archivedDataWithRootObject: records];
} else {
return nil;
}
}
- (BOOL) loadDataRepresentation: (NSData*) data ofType: (NSString*) type
{
if ([type isEqualToString: @"mon"]) {
[records setArray: [NSUnarchiver unarchiveObjectWithData: data]];
return YES;
}
return NO;
}
When the user saves the document, Document
will recieve
-dataRepresentationOfType:
. The type will be the NSName
of the NSType
in the property
list of this application (MoneyInfo.plist
), which is mon
. Here, we use NSArchiver
to
transform the whole NSArray
into NSData
, and we don’t need to worry about
the format of the file. When document is going to be loaded, it will call
-loadDataRepresentation:ofType:
. We use NSUnarchiver
to transform
the NSData
into NSArray
. That’s all. If you are using your own data
structure, you have to deal with data
archives
by yourself.
Here is a related article: Using the Property List Objects and the NSCoding Protocol.
Note
If you’ve ever used Python’s pickle
, NSArchiver
is similar, but doesn’t have the problem that pickles actually contain commands that are executed. NSArchiver
simply represents the data itself as a binary file, not as a list of commands to piece together the data like pickle
does.
The saved data is in binary format, which is usually undesired. Since we only use the basic data structure, we could save it into an OpenStep-style property list, which is human-readable.
Document.m:
- (NSData*) dataRepresentationOfType: (NSString*) type
{
if (type == nil) {
type = @"mon";
}
if ([type isEqualToString: @"mon"]) {
return [[records description]
dataUsingEncoding: [NSString defaultCStringEncoding]];
} else {
return nil;
}
}
- (BOOL) loadDataRepresentation: (NSData*) data ofType: (NSString*) type
{
if ([type isEqualToString: @"mon"]) {
NSString *string = [[NSString alloc]
initWithData: data
encoding: [NSString defaultCStringEncoding]];
[records setArray: [string propertyList]];
RELEASE(string);
return YES;
}
return NO;
}
I use -description to get the property list, which is NSString. Then use -dataUsingEncoding to transform property list (NSString) into NSData for saving. And do the opposite for the loading. Now, you can look at the saved file, which is very easy to read.
Here is the source code for the easy-read version: LoadSave-src.tar.gz.
Note
On macOS, this will save as an XML or binary property list, since macOS no longer uses OpenStep-style property lists.
If you are saving the document into a directory, such as if your app saves rich text documents with images, you may want to use
-loadFileWrapperRepresentation:ofType:
and
-fileWrapperRepresentationOfType:
. The methods -readFromFile:ofType:
and
-writeToFile:ofType:
offer the ability to access the file system
directly.