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.

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.