Virtually all applications need a way to make some of their objects persistent. For example, the Expenses application that you created in the previous section doesn’t save the state of the data model, so you’ll lose all of your expenses information as soon as you quit. Cocoa applications typically use coding and archiving to store document contents and other critical application data to disk for later retrieval. Some applications may also use coding and archiving to send objects over a network to another application. (For instance, in case you want to send a set of expenses to a friend.)
In this section you will modify the simple expense-tracking application from the previous section to use coding and archiving so it can save and reload the array of expenses.
Coding, as implemented by NSCoder, takes a connected group of objects such as those in an application (an object graph) and serializes that data, capturing the state, structure, relationships, and class memberships of the objects. A subclass of NSCoder, NSArchiver extends this behavior by storing the serialized data in a file.
When archiving an object graph’s root object, archive not only that
object, but all other objects the root object references, all objects
those second-level objects reference, etc. To be archived, though,
objects must conform to the NSCoding protocol (consisting of the
encodeWithCoder:
and
initWithCoder:
methods).
Open the main nib file in Interface Builder.
In the Instances pane of the
MainMenu.nib
window, Control-drag a connection from
the Window instance to the MyDataSource instance. In the Connections
pane of the Info window, make MyDataSource the delegate of
Window.
In Project Builder, open the TableView example project.
Open Expense.h
and modify the class
declaration as follows. Adding <NSCoding>
declares that the Expense class conforms to the coding
protocol.
@interface Expense : NSObject <NSCoding> {
Open Expense.m
and add the NSCoding methods. The
most frequently used NSCoder method, encodeObject:
,
encodes (serializes) a single object. For nonobject types, you can use
encodeValueOfObjCType:at:
. The order of decoding
should be the same as the order of encoding; since date is encoded
first, it should be decoded first. NSCoder defines
decode
methods that correspond to the
encode
methods, which you should use. As in any
init
method, end by returning
self
—an initialized instance:
- (id)initWithCoder:(NSCoder *)coder { [self setDate: [coder decodeObject]]; [self setCategory: [coder decodeObject]]; [self setAmount: [coder decodeObject]]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject: [self date]]; [coder encodeObject: [self category]]; [coder encodeObject: [self amount]]; }
Add the following implementation of the
windowShouldClose:
delegate method to
MyDataSource. This method first displays a standard alert asking users
if they want to save the contents of the window. If they click Save, a
Save dialog box (NSSavePanel) is displayed so the users can name the
file and choose a directory in which to save it. The
setRequiredFileType:
method tells the Save panel to
append .expenses
to the filename so that the file
has a recognizable type. If the user clicks Save, NSArchiver encodes
the entire array of Expense objects and saves them to disk. If the
user cancels, the window is closed and nothing is saved:
- (BOOL)windowShouldClose:(NSWindow *)sender { NSSavePanel *sp; int answer; answer = NSRunAlertPanel(@"Save Expenses", @"Do you want to save?", @"Save", @"Don't Save", nil); if (answer == NSAlertDefaultReturn) { sp = [NSSavePanel savePanel]; [sp setRequiredFileType:@"expenses"]; answer = [sp runModal]; if (answer == NSOKButton ) { [NSArchiver archiveRootObject:[self expenses] toFile:[sp filename]]; } } return YES; }
Finally, modify the implementation of awakeFromNib
so it prompts the users for a file to load. This method now presents a
standard open dialog box, so the users can choose an archived data
file to load. The types array means that the users can’t choose any
file, only valid expenses files (or at least files with a
.expenses
file extension). If they choose a file,
NSUnarchiver is invoked to restore the array of expense objects from
the archive. If the users cancel, the test data is generated as
before:
- (void)awakeFromNib { NSOpenPanel *op; int answer; op = [NSOpenPanel openPanel]; answer = [op runModalForTypes: [NSArray arrayWithObject:@"expenses"]]; if (answer == NSOKButton) { [self setExpenses: [NSUnarchiver unarchiveObjectWithFile: [op filename]]]; } else { [self setExpenses:[self generateTestData]]; } }
3.15.2.78