Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 58 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
58
Dung lượng
2,09 MB
Nội dung
CHAPTER 11: Basic Data Persistence380 Check that checkbox, and then click the Choose… button. When prompted, enter a project name of Core Data Persistence. Before we move on to our code, let’s take a look at the project window. There’s some new stuff here you’ve not seen before. Expand both the Classes and Resources folders (see Figure 11-6). Figure 11-6. Our project template with the files needed for Core Data Entities and Managed Objects Of course, we have a bunch of files you’re already familiar with: an application delegate, a MainWindow.xib, and an info property list. But, there’s another file in the Resources folder called Core_Data_Persistence.xcdatamodel. That is our data model. Core Data lets us design our data models visually, without writing code. Single-click that file now, and you will be pre- sented with the data model editor (see Figure 11-7). You may want to expand your Xcode window and hide the detail pane (⇧⌘E) while working with the data model editor. 24594ch11.indd 380 6/24/09 11:16:09 AM Download at Boykma.Com CHAPTER 11: Basic Data Persistence 381 Figure 11-7. Core Data’s data model editor. This is where you an create and edit data models. The traditional way to create data models in Cocoa is to create subclasses of NSObject and conform them to NSCoding and NSCopying so that they can be archived, as we did earlier in this chapter. Core Data uses a fundamentally different approach. Instead of classes, you cre- ate entities here in the data model editor, and then in your code, create managed objects from those entities. TIP The terms “entity” and “managed object” can be a little confusing, since both refer to data model objects. The term “entity” refers to the description of an object while “managed object” is used to refer to actual concrete instances of that entity created at runtime. So, in the data model editor, you create entities, but in your code, you will create and retrieve managed objects. The distinction between entities and managed objects is similar to the distinction between a class and instances of that class. An entity is made up of properties. There are four types of properties: attributes, relation- ships, fetched properties, and fetch requests. An attribute serves the same function in a core data entity as an instance variable does in an objective-C class. They both hold the data. 24594ch11.indd 381 6/24/09 11:16:09 AM Download at Boykma.Com CHAPTER 11: Basic Data Persistence382 As the name implies, a relationship defines the relationship between entities. For example, suppose you wanted to define a Person entity. You might start by defining a few attributes, like hairColor, eyeColor, height, and weight. You might define address attributes, like state and ZIP code, or you might embed those in a separate, HomeAddress entity. Using this latter approach, you’d then want to create a relationship between a Person and a HomeAddress. Relationships can be to one and to many. The relationship from a Person to a HomeAddress is probably “to one,” since most people only have a single home address. The relationship from HomeAddress to Person might be “to many,” since there may be more than one Person living at that HomeAddress. A fetched property is an alternative to a relationship. The main difference between them is in the way they affect loading. For example, if a Person has a relationship with a HomeAddress, when the Person is loaded, the HomeAddress will be loaded, too. Alterna- tively, if a Person references HomeAddress as a fetched property, when the Person is loaded, HomeAddress is not loaded, at least not until HomeAddress is accessed. Can you say “lazy loading”? A fetch request is a predefined query. For example, you might say, “Give me every Person whose eyeColor is blue.” Typically, attributes, relationships, and fetched properties are defined using Xcode’s data model editor. Fetch requests can be just as easily defined in the data model editor or in your code. In our Core Data Persistence application, we’ll build a simple entity so you can get a sense of how this all works together. For more detail on Core Data, check out the extensive coverage in More iPhone 3 Development. Key-Value Coding In your code, instead of using accessors and mutators, you will use key-value coding to set properties or retrieve their existing values. Key-value coding may sound intimidating, but it’s something you’ve already used quite a bit in this book. Every time we’ve used NSDictionary, for example, we were using key-value coding because every object in a dic- tionary is stored under a unique key value. The key-value coding used by Core Data is a bit more complex than that used by NSDictionary, but the basic concept is the same. When working with a managed object, the key you will use to set or retrieve a property’s value is the name of the attribute you wish to set. So, to retrieve the value stored in the attri- bute called name from a managed object, you would call: NSString *name = [myManagedObject valueForKey:@”name”]; 24594ch11.indd 382 6/24/09 11:16:10 AM Download at Boykma.Com CHAPTER 11: Basic Data Persistence 383 Similarly, to set a new value for a managed object’s property, you would do this: [myManagedObject setValue:@”Martha Stewart” forKey:@”name”]; Putting It All in Context So, where do these managed objects live? They live in something called a persistent store, which is sometimes also referred to as a backing store. Persistent stores can take several different forms. By default, a Core Data application implements a backing store as an SQLite database stored in the application’s documents directory. Even though your data is stored via SQLite, classes in the Core Data framework do all the work associated with loading and saving your data. If you use Core Data, you won’t need to write any SQL statements. You just work with objects, and Core Data figures out what it needs to do behind the scenes. In addi- tion to SQLite, backing stores can also be implemented as binary flat files. There’s also a third option to create an in-memory store, which you might use if writing a caching mechanism, but it doesn’t save data beyond the end of the current session. In almost all situations, you should just leave it as the default and use SQLite as your persistent store. Although most applications will have only one persistent store, it is possible to have multiple persistent stores within the same application. If you’re curious about how the backing store is created and configured, you can look right in your Xcode project at the file Core_Data_ PersistenceAppDelegate.m. The Xcode project template, we chose provided us with all the code needed to set up a single persistent store for our application. Other than creating it (which is handled for us in our application delegate), we gener- ally won’t work with our persistent store directly, but rather will use something called a managed object context, often just referred to as a context. The context intermediates access to the persistent store and maintains information about what properties have changed since the last time an object was saved. The context also registers all changes with the undo manager, meaning that you always have the ability to undo a single change or roll back all the way to the last time data was saved. You can have multiple contexts pointing to the same persistent store, though most iPhone applications will only use one. You can find out more about using multiple contexts and the undo manager in More iPhone 3 Develop- ment as well. Many core data calls require an NSManagedObjectContext as a parameter, or have to be executed against a context. With the exception of very complicated, multithreaded iPhone applications, you can just use the managedObjectContext property from your application delegate, which is a default context that gets created for you automatically, also courtesy of the Xcode project template. 24594ch11.indd 383 6/24/09 11:16:10 AM Download at Boykma.Com CHAPTER 11: Basic Data Persistence384 NOTE You may notice that, in addition to a managed object context and a persistent store coordinator, the provided application delegate also contains an instance of NSManagedObjectModel. This class is responsible for loading and representing, at runtime, the data model you will create using the data model editor in Xcode. You generally won’t have to interact directly with that class. This class is used behind the scenes by the other Core Data classes so they can identify what entities and properties you’ve defined in your data model. As long as you create your data model using the provided file, there’s no need to worry about this class at all. Creating New Managed Objects Creating a new instance of a managed object is pretty easy, though not quite as straightfor- ward as creating a normal object instance using alloc and init. Instead, you use a factory method on a class called NSEntityDescription. Instances of this class represent a single entity in memory. Remember: entities are like classes. They are a description of an object, and define what properties a particular entity has. To create a new object, we do this: theLine = [NSEntityDescription insertNewObjectForEntityForName:@”EntityName” inManagedObjectContext:context]; The method is called insertNewObjectForEntityForName:inManagedObjectContext: because, in addition to creating the object, it inserts the newly create object into the context and then returns that object autoreleased. After this call, the object exists in the context but is not yet part of the persistent store. The object will get added to the persistent store the next time the managed object context’s save: method is called. Retrieving Managed Objects To retrieve managed objects from the persistent store, you create a fetch request and pro- vide that request with an NSEntityDescription that specifies the entity of the object or objects you wish to retrieve. Here is an example that creates a fetch request: NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription *entityDescr = [NSEntityDescription entityForName:@”EntityName” inManagedObjectContext:context]; [request setEntity:entityDescr]; Optionally, you can also specify criteria for a fetch request using the NSPredicate class. A predicate is similar to the SQL where clause and allows you to define the criteria used to determine the results of your fetch request. 24594ch11.indd 384 6/24/09 11:16:10 AM Download at Boykma.Com CHAPTER 11: Basic Data Persistence 385 NOTE Learn Objective-C on the Mac by Mark Dalrymple and Scott Knaster (Apress, 2008) has an entire chapter devoted to the use of NSPredicate. Here is a simple example of a predicate: NSPredicate *pred = [NSPredicate predicateWithFormat:@”(name = %@)”, nameString]; [request setPredicate: pred]; The predicate created by the first line of code would tell a fetch request that, instead of retrieving all managed objects for the specified entity, retrieves just those where the name property is set to the value currently stored in the nameString variable. So, if nameString were an NSString that held the value @”Bob”, we would be telling the fetch request to only bring back managed objects that have a name property set to “Bob”. This is a simple example, but predicates can be considerably more complex and can use Boolean logic to specify the precise criteria you might need in most any situation. After you’ve created your fetch request, provided it with an entity description, and option- ally given it a predicate, you execute the fetch request using an instance method on NSManagedObjectContext: NSError *error; NSArray *objects = [context executeFetchRequest:request error:&error]; if (objects == nil) { // handle error } executeFetchRequest:error: will load the specified objects from the persistent store and return them in an array. If an error is encountered, you will get a nil array, and the error pointer you provided will point to an NSError object that describes the specific problem. If there was no error, you will get a valid array, though it may not have any objects in it, since it is possible that there are none that meet the specified criteria. From this point on, any changes you make to the managed objects returned in that array will be tracked by the managed object context you executed the request against and saved when you send that context a save: message. Let’s take Core Data for a spin now. Designing the Data Model Let’s return our attention to Xcode and create our data model. Single-click Persistence_Core_ Data.xcdatamodel to open Xcode’s data model editor. The upper-left pane of the data model 24594ch11.indd 385 6/24/09 11:16:10 AM Download at Boykma.Com CHAPTER 11: Basic Data Persistence386 editor is called the entity pane because it lists all the entities that are currently in your data model. It’s an empty list now, because we haven’t created any yet (see Figure 11-8). Remedy that by clicking the plus icon in the lower-left corner of the entity pane, which will create and select an entity titled Entity. If you look in the bottom pane of the data model editor, you’ll notice that it’s no longer empty (see Figure 11-9)! As you build your data model using the top three panes (collectively called the browser view), a graphical representation of your data model is shown in the bottom portion of the screen, which is called the diagram view. If you prefer working graphically, you can actually build your entire model in the diagram view. Right- clicking the background of the diagram view will bring up a contextual menu that will allow you to add entities and change the diagram view’s appearance. Right-clicking an entity will bring up a menu that allows you to add properties to the selected entity. We’re going to stick with the browser view in this chapter because it’s easier to explain, but when you’re creating your own data models, feel free to work in the diagram view if that approach suits you better. Figure 11-9. Xcode’s data model editor’s diagram view shows an editable graphical representation of your data model. Figure 11-8. The upper left-pane of the data model editor is the entity pane. Click- ing the plus icon in the lower left corner adds an entity 24594ch11.indd 386 6/24/09 11:16:10 AM Download at Boykma.Com CHAPTER 11: Basic Data Persistence 387 The upper-right pane of the data model editor is called the detail pane. Part of the reason we had you close Xcode’s detail pane a minute ago was to avoid confusion caused by having two unre- lated detail panes. Throughout the rest of the chapter, when we refer to the detail pane, we’ll be referring to the data model editor’s detail pane (see Figure 11-10), not Xcode’s detail pane. The data model editor’s detail pane allows you to edit the currently selected entity or property. At the moment, the detail pain shows informa- tion about the entity we just added. Change the Name field from Entity to Line. You can ignore the other fields in the detail pane for now. Those other fields will come into play when creating more complex data models, like those discussed in More iPhone 3 Development. The data model editor’s upper-middle pane is the property pane (see Figure 11-11). As its name implies, the property pane allows you to add new properties to your entity. Notice that plus sign in the lower-left corner of the property pane features a little black triangle. If you click the triangle and hold the mouse button down, a pop-up menu will appear, allow- ing you to add an attribute, fetched property, relationship, or fetch request to your entity (see Figure 11-12). Select Add Attribute from the menu that popped up. A new attribute creatively named newAttrib- ute should have just been added to your properties pane and selected. In the detail pane, change the new attribute’s name from newAttribute to lineNum and change its Type from Undefined to Integer 16, which turns this attribute into one that will hold an integer value. We will be using this attribute to identify which of the four fields the managed object holds data for. Since we only have four options, we selected the smallest integer type available. There are three checkboxes below the Name field. The leftmost one, Optional, should cur- rently be selected. Click it to deselect it; we don’t want this attribute to be optional. A line that doesn’t correspond to a label on our interface is useless. Don’t worry about the other Figure 11-10. The data model editor’s detail pane, not to be confused with Xcode’s detail pane Figure 11-11. The property pane in Xcode’s data model editor. This is where you can add properties to the currently selected entity. Figure 11-12. Clicking the plus icon in the property pane brings up a menu of options. 24594ch11.indd 387 6/24/09 11:16:10 AM Download at Boykma.Com CHAPTER 11: Basic Data Persistence388 two checkboxes for now. Transient attributes are used to store nonstandard objects for which there is no predefined attribute type. Selecting the Indexed checkbox will cause an index in the underlying SQL database to get created on the field that holds this attribute’s data. Click the plus-icon, and select Add Attribute again, this time creating an attribute with the name lineText and changing its Type to String. This attribute will hold the actual data from the text field. Leave the Optional checkbox checked for this one; it is altogether possible that the user won’t enter a value for a given field. When you changed the Type to String, you’ll notice that additional options came up that would let you set a default value or limit the length of the string. We won’t be using any of those options for this application, but it’s nice to know they’re there. Guess what? Your data model is done. That’s all there is to it. Core Data lets you point and click your way to an application data model. Let’s finish building the application so we can see how to use our data model from our code. Creating the Persistence View and Controller Because we selected the window-based application template, we weren’t provided with a view controller. So single-click the Classes folder, and press ⌘N or select New File… from the File menu to bring up the new file assistant. Select UIViewController subclass from the Cocoa Touch Class heading, and name the file PersistenceViewController.m, making sure to have it create PersistenceViewController.h as well. Also make sure to check the box that says With XIB for user interface to have it create a nib file for you automatically. If PersistenceViewController. xib was placed in your Classes folder, drag it down to the Resources folder so that our project stays nice and organized. Single-click PersistenceViewController.h, and make the following changes, which should look very familiar to you: #import <UIKit/UIKit.h> @interface PersistenceViewController : UIViewController { UITextField *line1; UITextField *line2; UITextField *line3; UITextField *line4; } @property (nonatomic, retain) IBOutlet UITextField *line1; @property (nonatomic, retain) IBOutlet UITextField *line2; @property (nonatomic, retain) IBOutlet UITextField *line3; @property (nonatomic, retain) IBOutlet UITextField *line4; @end 24594ch11.indd 388 6/24/09 11:16:10 AM Download at Boykma.Com CHAPTER 11: Basic Data Persistence 389 Save, and double-click PersistenceViewController.xib to open Interface Builder. Design the view, and connect the outlets by following the instructions from earlier in this chapter in the “Designing the Persistence Application View” section. Once you’re done, save the nib file, and go back to Xcode. In PersistenceViewController.m, insert the following code at the top of the file: #import “PersistenceViewController.h” #import “Core_Data_PersistenceAppDelegate.h” @implementation PersistenceViewController @synthesize line1; @synthesize line2; @synthesize line3; @synthesize line4; - (void)applicationWillTerminate:(NSNotification *)notification { Core_Data_PersistenceAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; NSManagedObjectContext *context = [appDelegate managedObjectContext]; NSError *error; for (int i = 1; i <= 4; i++) { NSString *fieldName = [NSString stringWithFormat:@”line%d”, i]; UITextField *theField = [self valueForKey:fieldName]; NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@”Line” inManagedObjectContext:context]; [request setEntity:entityDescription]; NSPredicate *pred = [NSPredicate predicateWithFormat:@”(lineNum = %d)”, i]; [request setPredicate:pred]; NSManagedObject *theLine = nil; NSArray *objects = [context executeFetchRequest:request error:&error]; if (objects == nil) { NSLog(@”There was an error!”); // Do whatever error handling is appropriate } if ([objects count] > 0) theLine = [objects objectAtIndex:0]; else theLine = [NSEntityDescription 24594ch11.indd 389 6/24/09 11:16:10 AM Download at Boykma.Com [...]... Quartz and OpenGL the bottom half of the canvas blue, the canvas will be half red and half either blue or purple Blue if the paint is opaque; purple if the paint is semitransparent Quartz’s virtual canvas works the same way If you paint the whole view red, and then paint the bottom half of the view blue, you’ll have a view that’s half red and half either blue or purple, depending on whether the second drawing... need to create an object, we can just set the view’s shapeType property to the segment index from sender Recall the ShapeType enum? The four elements of the enum correspond to the four toolbar segments at the bottom of the application view We set the shape to be the same as the currently selected segment, and we hide and unhide the colorControl based on whether the Image segment was selected Updating... fully understand the rest of the code here We’ll get into the details of working with touches and the specifics of the touchesBegan:withEvent:, touchesMoved:withEvent:, and touchesEnded:withEvent: methods in Chapter 13 In a nutshell, these three methods inherited from UIView (but actually declared in UIView’s parent UIResponder) can be overridden to find out where the user is touching the iPhone s screen... touchesMoved:withEvent:, gets continuously called while the user is dragging a finger on the screen All we do here is store off the new location in lastTouch and indicate that the screen needs to be redrawn The last one, touchesEnded:withEvent:, gets called when the user lifts that finger off of the screen Just like in the touchesMoved:withEvent: method, all we do is store off the final location in the lastTouch... real feel for the difference between the two Figure 12-1 Our chapter’s simple drawing application in action Download at Boykma.Com 24594ch12.indd 39 8 6/25/09 6:06:10 PM CHAPTER 12: Drawing with Quartz and OpenGL 39 9 The application features a bar across the top and one across the bottom, each with a segmented control The control at the top lets you change the drawing color, and the one at the bottom... Navigation Bar in the library Make sure you are grabbing a Navigation Bar— not a Navigation Controller We just want the bar that goes at the top of the view Place the Navigation Bar snugly against the top of the view window, just beneath the status bar Next, look for a Segmented Control in the library, and drag that right on top of the Navigation Bar Drop it in the center of the nav bar, not on the left or... useRandomColor; @end The first thing we do is import the Constants.h header we just created so we can make use of our enumerations We then declare our instance variables The first two will track the user’s finger as it drags across the screen We’ll store the location where the user first touches the screen in firstTouch We’ll store the location of the user’s finger while dragging and when the drag ends in... Save and close the nib, and go back to Xcode Note You may have wondered why we put a navigation bar at the top of the view and a toolbar at the bottom of the view According to the iPhone Human Interface Guidelines published by Apple, navigation bars were specifically designed to be placed at the top of the screen and toolbars are designed for the bottom If you read the descriptions of the Toolbar and... start drawing, we need to add the segmented controls to our nib and then hook up the actions and outlets Double-click QuartzFunViewController.xib to open the file in Interface Builder The first order of business is to change the class of the view, so single-click the View icon in the window labeled QuartzFunViewController.xib, and press ⌘4 to open the identity inspector Change the class from UIView to QuartzFunView... drawing code will use these two variables to determine where to draw the requested shape Next, we define a color to hold the user’s color selection and a ShapeType to keep track of the shape the user wants drawn After that is a UIImage property that will hold the image to be drawn on the screen when the user selects the rightmost toolbar item on the bottom toolbar (see Figure 12-6) The last property is . OpenGL 39 9 The application features a bar across the top and one across the bottom, each with a seg- mented control. The control at the top lets you change the drawing color, and the one at the. archives. We then made a change and used the iPhone s built-in SQLite3 mechanism to save the application data. Finally, we rebuilt the same application using Core Data. These mechanisms are the basic. Drawing with Quartz and OpenGL3 98 the bottom half of the canvas blue, the canvas will be half red and half either blue or purple. Blue if the paint is opaque; purple if the paint is semitransparent. Quartz’s