8.2. Property List FilesLet's investigate the property list file format by writing a .plist file of our own. Both Cocoa and Core Foundation provide methods for converting their property list values directly to the .plist format, so writing the file will be a simple matter of extending our model to provide a property list type; our views, to include a user action to trigger the writing of the file; and our controller, to link the two. 8.2.1. Writing a Property ListModelOur first task is to add to Linear's data model the ability to express itself in property list types. We'll do this by adding categories to the Regression and DataPoint classes. Categories are an Objective-C mechanism for adding methods to a classany class, even those supplied by Cocoawithout disturbing the core implementation of the class. (Realistically, there's no reason we shouldn't add our new methods directly to Regression and DataPoint. The original code is under our control, and nobody is relying on the old version.) In Xcode, with the Linear project open, press command-N to bring up the New File Assistant. The variety of starter content Xcode offers for new files is extensive but doesn't cover category files. Select Cocoa Objective-C class, and type Regression-PropertyList as the base file name. Make sure that the box is checked for generating a header file. Xcode now presents you with a skeleton class declaration for an NSObject subclass called Regression_PropertyList. Edit the declaration to look like this: #import "Regression.h" We declare an interface for an additional category, named PropertyList, on an existing class (Regression) containing the single method -asPropertyList. To save yourself the trouble of retyping it, copy the line - (NSDictionary *) asPropertyList; Press command-option-up arrow to switch your view to the .m file. Once again, the template we're given has to be edited a bit, but it's close. Regression-PropertyList.m should look like this: #import "Regression-PropertyList.h" In the first part of the asPropertyList method, we build up an NSMutableArray containing the property list version of each data point. Then we build an NSDictionary with four keyspoints, slope, intercept, and correlationto identify the point list and the respective statistics. Note the use of NSNumber as the class embodying the simple number type for use in property lists. The DataPoint-PropertyList.h header file uses the same principles: #import "DataPoint.h" The same is true for the DataPoint-PropertyList.m implementation file: #import "DataPoint-PropertyList.h" Because all the objects created by the asPropertyList methods come from class convenience methods, not from methods with new, alloc, or copy in their names, we know that they are autoreleased, and we need not worry about releasing them ourselves. ViewNow that we have a way to get our model into property list form, we need away to make use of it. The easiest way is to add a menu command that saves the active document as a property list. (The right way would be to add .plist as a supported document type for the Linear application and to add support for it to the dataRepresentationOfType: and loadDataRepresentation:ofType: methods in MyDocument, but for instructional purposes, we'll do it the easy way.) Open the MainMenu.nib Interface Builder file (Figure 8.1). The quickest way to do this is to click on the Project (top) icon in the Groups & Files list, then double-click MainMenu.nib as it appears in the detail list to the right. Figure 8.1. The MainMenu.nib file as it first opens. In a document-based application, the main menu Nib contains only the menu bar, represented by the small window at the top and the icon at the right of the Nib window. The other two icons are placeholders for objects outside the Nib.MainMenu.nib contains only the application menu bar, which appears in a small window containing the menu titles as you'd see them in the application's menu bar (Figure 8.1).Clicking a title drops down a representation of that menu, identical except as hidden items are visible in the Interface Builder display (Figure 8.2). Figure 8.2. The File menu in MainMenu.nib expands when you click its title in the menu window. All items, including hidden ones, are visible in the menu as shown. Menu titles and key equivalents can be edited in place, and you can drag additional items into the menu from the Menus palette.We want to add a Save as PList . . . item to the File menu. (We include an ellipsis after the label because we'll be presenting a save-file dialog that will allow the user to cancel.) Click the title of the File menu so it drops down, as shown in Figure 8.2. In the Interface Builder object palette at the upper right of your screen, select the first page, to show the available menus and menu items. Drag the object labeled Item from the palette to the opened File menu, and drop it just below the Save As . . . item. (If you drop it somewhere else, it doesn't matter, but if it bothers you, you can drag it from where you dropped it to where you wanted it to go.)
With the item in place, double-click the title (Item) and type Save as PList . . . as the new title. Press Return to end the edit. Now we have a menu item. How do we make it do something? We know that Cocoa objects that send user commands keep two pieces of information: the action (what is to be done) and the target (what is to do it). The action is the name of an Objective-C method taking one parameter: an identifier ending with a colon. We haven't written the action method yet, but we can make up a name for it: saveAsPList:. What about the target? To what Cocoa object will we assign the task of responding to saveAsPList:? I don't know, says a stubborn part of our subconscious, anything that wants to, I guess. This turns out not to be a stupid answer in Cocoa. Cocoa keeps a continually updated responder chain, a series of potential responders to user actions. The chain begins at the First Responder, which may be the selected view in the front window, and then proceeds to the front window, the window's document, and finally to the application itself. You have probably noticed that the second icon in an Interface Builder Nib window represents the First Responder. A user interface element can designate the First Responder, whatever it may be at the time, as the target of its action, and Cocoa will shop the action up the responder chain until it finds an object that can handle it. The responder chain is a little more complicated than that. For more information, consult Apple's documentation for NSApplication, NSResponder, and the related Programming Topic articles. If we tried control-dragging an action link from our new menu item to the First Responder icon, we'd quickly be balked. Interface Builder would present an Inspector for the link, asking us to designate the method selector for our desired action. The list it presents does not include saveAsPList:, because we just made that up. Before we make the link, we have to tell Interface Builder that saveAsPList: is a possible action. Interface Builder allows you to do this by editing the First Responder as if it were a custom class. Click the Class tab in the MainMenu.nib window. Scroll the class list all the way to the left, and select NSObject in the first column. You will find First Responder listed among NSObject's subclasses. Click First Responder (see Figure 8.3). You now want an Inspector on the First Responder "class." Select Show Inspector from the Tools menu. The Inspector window shows a tab for Actions, under which are listed all the known actions that NSResponder subclasses respond to. To add our new action, click the Add button, and type saveAsPList: in the new line that results. Press the Return key to make your edit final. Figure 8.3. Finding the First Responder "class" in the Nib's class listing. Select NSObject in the leftmost column of the class browser, and click First Responder in the second column.Be sure to include the colon at the end of the selector saveAsPList:. Objective-C considers both saveAsPList and saveAsPList: to be legal, but completely different, method names. Forgetting the colon in action methods is one of the commonest errors Cocoa programmers make. Now we can hook up our menu item. Click the Instances tab in the MainMenu.nib window to make the First Responder icon visible, and arrange the Nib window and the menu bar window so that you can see both the new item and the First Responder icon. When everything is in place, hold down the control key and drag from the new menu item to the First Responder icon. When the icon highlights, release the mouse button (Figure 8.4). Figure 8.4. Linking a menu item to the First Responder. Control-drag from the new menu item to the First Responder icon. When the icon highlights, release the mouse button. The line between the two shows that the link has been made. The Inspector for the menu item appears, offering action methods for the link.[View full size image] If it had been hidden before, the Inspector window now appears, showing a list of all the methods known for the responder chain. Our saveAsPList: method is in the list, because we added it; Interface Builder guesses that saveAsPList: is the right action for a menu item named Save as PList . . . and highlights that action, saving us the trouble of hunting for it. Click the Connect button at the bottom of the Inspector window, and the link is made. ControllerWhen we pulled the name of the saveAsPList: method out of the air, we had no particular implementer in mind for it, but now we have to think about it.
Fortunately, we have one such class: MyDocument. As a document class, it is on the responder chain. We've already made it the controller class associated with our Regression model. And, it exists. We open MyDocument.m and add this line to the beginning: #import "Regression-PropertyList.h" We also add the following lines somewhere in the implementation section: - (IBAction) saveAsPList: (id) sender Objective-C methods do not have to be declared in advance to be legal or usable by other objects. With all this hooked up and compiled, we run Linear and fill the data table with the old familiar not-quite y = 2x: 1.0 2.05 We click the Compute button to update the statistics (2.00196, 0.0175146, and 0.999871, as before) and pull down the File menu to select our new Save As PList . . . command. A save-file sheet appears, most likely offering to save a file named Untitled in your home directory. You can change this if you like, but be sure that you put the file somewhere you can find it, because we will be inspecting its contents. 8.2.2. Examining Property ListsAs TextWhen the save is done, we can quit Linear. Now go to the Finder and find the file you just saved. Drag its icon onto the Xcode icon in your dock. Xcode presents you with a text editor on the contents of the file, which looks something like this: <?xml version="1.0" encoding="UTF-8"?> You likely will be relieved to see that the property list file format is XML and that Cocoa's built-in writer for .plist files indents them fairly nicely. The top-level element is <plist>, which must contain one property list elementin this case, <dict>, for our Regression dictionary. A<dict> element's contents alternate between <key> string elements and property list value elements. One of the keys in the Regression dictionary, points, has an <array> value. An <array> may contain zero or more property list elements, of any type, though in this case, they are all <dict>s from our DataPoint objects. The nice thing about XML is that it is standard: Correct XML will be accepted by any consumer of a document type definition, regardless of the source. A .plist file generated by Cocoa will be treated the same as one generated by a text editor. The difficult thing about XML is that it must be correct. If you forget to close an element or miss the strict alternation of <key> and property list elements in <dict> lists, you will get nothing out of Apple's parser. There are three ways to cope with this restriction. First, you can always start your own .plist files by editing a known good .plist file. It's difficult to omit the processing instruction or the <plist> skeleton if they are already in the file. Second, you can use the macro or glossary facilities of your text editor to create a document skeleton and wrap your entries in proper tags. Bare Bones Software's BBEdit comes with a .plist glossary for just this purpose. Xcode 2.2, surprisingly, does not include property list macros. We'll be fixing that later in this chapter. Third, you can use the Property List Editor application, found among the Xcode tools at /Developer/Applications/Utilities (Figure 8.5). The simplest use of Property List Editor is as a check on your text-edited file. Simply attempt to open your file with Property List Editor. If the file doesn't open, something's wrong. If the error isn't obvious, find a way to cut about half the list to the clipboard, leaving what, you hope, would still be a legal property list. Try opening the file with Property List Editor again. If the file opens, the error is in the part on the clipboard; if not, it's in the part still in the file. In either case, paste the missing lines back into the file, and cut half the elements out of the half of the file in which you isolated the problem in the previous pass. Repeat this process, reducing your search by halves, until you arrive at a stretch of XML small enough to proofread. Figure 8.5. The Property List Editor application, found in the Utilities folder of the Developer Applications, presents the contents of property list files in a visual hierarchy that you may find easier to work with than the raw XML of the file itself. Trying to open a file with PLE is a good way to check whether the file contains a valid property list.[View full size image] One of the commonest errors is forgetting that the text portions of the property list XML are parsed character data, which means that < and & must be represented by < and &. Property List EditorOf course, you could simply use Property List Editor (PLE) to create your .plist files in the first place. Select New in the File menu (or press command-N) to open a window on a new, empty list. Add a root element to the list by clicking the New Root button. A line labeled Root appears under PropertyList with Dictionary listed under Class. Dictionary appears as a pop-up menu; clicking the mouse in the class cell shows the full range of choices of property list types. The button at the upper left of the window is the New button. Its meaning changes depending on what is selected in the list.
Because it is a container, a dictionary, the root element line can be opened. It is closed, so the rules say that the New operation should be New Sibling, but there can't be more than one root element, so the New Sibling button is disabled. Open the root element, and click New Child three times. This creates three new key/value pairs, with the keys in the left column, the types of the values (String) in the middle, and the values themselves (empty) in the right. Name these key/value pairs Ingredients, Material, and Method; make the first two dictionaries and the third an array. Open the Ingredients dictionary, and click New Child. Make the child's key Eggs and its type Dictionary. This dictionary, in turn, should have the key/string pairs unit count and quantity 3. Take care to change the type of the quantity value to Number before setting it. Add more siblings to Eggsor children to Ingredients as shown in Table 8.2.
The Material dictionary should be simple key/string pairs, as shown in Table 8.3. The Method array should contain strings, as shown in Table 8.4.
If you followed along with this exercise (see Figure 8.6), you've probably been persuaded that the Property List Editor has its advantages and disadvantages. On the plus side, it always generates correct property lists, and no matter how complex your property list structure becomes, PLE makes it easy to navigate. On the minus side, PLE was never meant for creating large, repetitious lists. By the third ingredient, you were probably wishing you could duplicate the quantity/numberunit/string dictionary pattern. Instead, you were forced to use the keyboard, the New button, and the Class pop-up. That PLE keeps dictionary keys in alphabetical order at all times is handy when you're browsing a property list, but it's a misfeature when you change a key and the line you are working on moves elsewhere on the screen. Figure 8.6. Construction of the Omelet Property List[View full size image] |
Friday, October 30, 2009
Section 8.2. Property List Files
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment