Chapter 15. Data Modeling

WHAT'S IN THIS CHAPTER?

  • Create a data schema

  • Define entities, properties, and relationships

  • Migrate between data schema versions

  • Quickly create a user interface

Data modeling is a visual tool for defining data objects and their relationships, called a data schema. A schema defines entities that contain properties. Properties can be values or relationships to other entities. The data modeling tools in Xcode let you create and edit a data model, as shown in Figure 15-1. The data model then becomes a resource that can be used by your application at run time.

To use data modeling effectively, you must have a rudimentary understanding of Core Data. If you are not familiar with Core Data, start by perusing the Core Data Programming Guide. You can find it in the Xcode documentation, or you can browse it online at http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CoreData/.

This book is not about database development or Core Data programming, but like the chapter on Interface Builder, it's difficult to explain how to use the data modeling tools without describing what you're doing and why. This chapter focuses on the Xcode tools you use to develop and maintain data models. In the process, I'll explain the data model's relationship to the run time environment and touch on some best practices. Just know that the modeling tools are the tip of the Core Data iceberg; Core Data can be extended extensively with custom code — all well beyond the scope of this book. If you have questions, refer to the Core Data Programming Guide.

FIGURE 15-1

Figure 15-1. FIGURE 15-1

TECHNOLOGY

Data modeling in Xcode has similarities to both class modeling and Interface Builder.

The user interface for data modeling is essentially the same as what you've used for class modeling, with one very important difference: in data modeling, you actually create and edit the objects in the data model. It is a true editor, not just a fancy visualization tool.

Note

The user interface for data modeling is almost identical to the interface used for class modeling; so similar that discussion of the common features has been omitted from this chapter. If you haven't read Chapter 14, I strongly urge you to at least browse through the "Class Model Browser," "Class Model Diagram," and "Editing a Class Model" sections. For the most part, simply substitute the words "entity" for "class" and "property" for "member."

From a conceptual standpoint, data modeling is most like Interface Builder: you graphically define objects and relationships. These definitions are stored in a data file that is included in the application's bundle. At run time, the data is read and objects are created. In Interface Builder, the objects are archived in a nib document. In data modeling, the data schema is deployed in a mom (Managed Object Model) file, produced by the data model compiler.

Entities (the containers you define in a data model) are essentially class definitions: They define the form from which any number of instances are created. Unlike Interface Builder, you don't define the instances in the class model. Instances are created dynamically to represent the records in your database. The number of instances of the Employee entity that get created is a function of how many employee records are read or created at run time. A human resources application that tracks employees might create 50,000 Employee objects for a large company or none at all when used by a sole proprietor.

Every instance of an entity becomes an instance of an NSManagedObject at run time. If you need to add functionality to your entities — beyond what's possible using the data modeling tools — you can define your own subclasses of NSManagedObject to be used instead. The "Creating NSManagedObject Subclasses" section toward the end of this chapter explains how that's done.

TERMINOLOGY

One of the things that can be immediately confusing about data modeling is the terminology. The data schema concepts are so much like classes and objects that it's hard to see why the same vocabulary isn't used. The reason is largely historical: Database design and computer programming had independent origins. Later, the two embraced many of the same concepts, ultimately merging to form Object-Oriented Database Management Systems (ODBMS). Though many of the concepts of object-oriented programming (OOP) and object-oriented databases (OODB) are the same, they retain their original lexicons.

Despite their resemblance, significant differences still exist between entities and classes, and the different monikers help reinforce that. Just keep these two principles in mind:

  • At development time, you are designing a database.

  • At run time, managed objects are created to contain the data in that database.

The following table defines common terms used in Core Data and data modeling:

TERM

DEFINITION

Entity

An entity defines a container of properties. An entity can also contain other auxiliary data such as user-defined values and predefined fetch requests. Defining an entity is similar to defining a class or a table in a database. At run time, instances of an entity are embodied by instances of NSManagedObject.

Property

The generic term for a member of an entity. The properties of an entity can be attributes, relationships, or predefined queries.

Attribute

A value in an entity. In a class, this would be an instance variable. In a database, this would be a field. Values store primitive, atomic values such as strings and integers.

Relationship

A connection between an entity and other entities. Relationships can be one-to-one or one-to-many. A Person entity might have two relationships, mother and father, both of which would be one-to-one relationships. The same entity might have a cousin relationship. This would be a one-to-many relationship that connects that entity to all of its cousins — which might be dozens or none at all. Relationships can be defined in the actual storage using any number of techniques, but typical database tools would use foreign keys or junction tables.

Inverse Relationship

If entity A has a relationship to entity B, and entity B has a reflexive relationship back to entity A, these two relationships are considered to be the inverse of each other. The data modeler can recognize inverse relationships, and uses that to simplify the data model diagram and to highlight bidirectional connections.

Fetched Property

A fetched property is like a relationship in that it connects an entity to some other set of entities. Unlike a regular relationship, fetched properties are based on a fetch request. A Person entity might have a relationship called siblings. It might then define a fetched property named sisters, which would be defined as all of the siblings where sex == "female".

Fetch Request

A fetch request is a predefined query, usually created with the predicate builder. A fetch request defines some criteria, such as person.age < 21, that can be used to filter entities.

CREATING A DATA MODEL

If you started your Xcode project using one of the Core Data application templates, your project already has a data model document, and you can skip to the next section.

Note

If you're new to Core Data, I highly recommend that you start by creating your first data model via one of the Core Data project templates — if only to examine what it does. There's a modest amount of "plumbing" that has to be connected before your data model will work at all. Specifically, NSManagedObjectContext, NSPersistentStoreCoordinator, and NSManagedObjectModel objects have to be created, configured, and shutdown properly. The Core Data project templates do all of these basics already and make a great place to start.

Create a new data model and add it to your project by choosing the File

CREATING A DATA MODEL
FIGURE 15-2

Figure 15-2. FIGURE 15-2

If your project already has classes that represent your data model, or you've already started your Core Data design by creating subclasses of NSManagedObject, Xcode can import your class definitions and — as best as it can — use those to create an equivalent set of entities. If you're starting your data model from scratch, just skip this step.

On the left are the project's source files and groups. Select a set of sources, and Xcode scans those source files for class definitions. The classes it finds are listed in the center column. Click the Add All button to add them all to the data model, or select specific classes and use the Add button to add them selectively. Repeat this process until you've added all of the classes you want included in the model. If you add too many classes, select them in the right column and click the Remove button to forget them.

Click the Finish button to create the model and add it to the project. For every class added to the Selected Classes list, Xcode creates an entity with that class name and implementation. As mentioned earlier, entities based on classes other than NSManagedObject must ultimately be a subclass of NSManagedObject. In other words, you can use your existing classes as the basis for your new data model, but before your application will work you'll have to change those classes so they are subclasses of NSManagedObject. The section "Creating NSManagedObject Subclasses" explains both how to subclass NSManagedObject and why you might not need to.

Creating Entities

The first thing to do is create entities — if you haven't already. Underneath the Entity list are plus (+), minus (−), and disclosure buttons. Click the plus button to create a new entity. Alternatively, choose Design

Creating Entities
FIGURE 15-3

Figure 15-3. FIGURE 15-3

In the upper-right corner of the details pane is a small tab control that switches between several different groups of settings. The different groups are similar to the different inspectors in Interface Builder; each edits a particular aspect of the entity (or property). The leftmost settings tab is the details pane of primary interest. I'll also discuss some of the migration tab (wrench icon) settings later in this chapter. You can find descriptions of the other settings in the Core Data Programming Guide.

Newly created entities have a class of NSManagedObject. If the entity is implemented by a custom class, enter the name of your custom class in the Class field. At run time, an instance of that class is created instead.

Like classes, entities can inherit from other entities. You can define common properties of several entities in a superentity, with subentities filling out the differences. If an entity extends the definition of another entity, select its superentity in the Parent menu. A superentity that exists only as a base for subentities — one that is never used on its own — can be marked as Abstract. Abstract entities are never created at run time.

To delete one or more entities, select them and click the - button, press the Delete key, or choose Edit

FIGURE 15-3

Creating Properties

Entities aren't too interesting until they contain something. To add properties to an entity, begin by selecting the entity in the browser or diagram. The list of existing properties appears in the middle column of the browser. Click the + button below the Properties list and choose the Add Attribute, Add Relationship, or Add Fetched Property command. If you don't see this menu, choose Show All Properties from the disclosure menu to the right of the − button. Alternatively, you can choose the Design

Creating Properties

Note

Interestingly, you can select multiple entities and add a new property to every one using any of the Add Property commands. For example, selecting three classes and choosing Add Attribute adds a new attribute to all three classes. So if you needed to add a tag attribute to eight different entities, you could do it with a single Add Attribute command by selecting all eight entities before you began.

Adding any property creates the kind of property you chose and gives it a generic name. Edit the name, type, and other specifics of the new property in the details pane. To rename a property, you can also double-click its name in either the browser list or in the diagram. All property names should begin with a lowercase letter. The three principal kinds of properties are described in the following sections.

Attributes

Attribute properties contain discrete values, and are the basic building blocks of your data. The types of data that Core Data supports are listed in the Type menu. Select the data type for the attribute from the list. Depending on which type you select, additional validation fields appear, as shown in Figure 15-4. The validation settings are used to determine if the value stored, or attempting to be stored, in this attribute is allowed.

FIGURE 15-4

Figure 15-4. FIGURE 15-4

The scalar numeric types (Integer, Decimal, Float, and Date) define minimum and maximum values. String attributes can have minimum and maximum lengths. Alternatively, string attributes can be matched against a regular expression. If the regular expression matches the string, the string is valid. Other types, like Boolean and Binary Data, have no constraints.

An attribute can be Optional. If this is checked, the attribute is allowed to be completely absent (nil) when stored. Entities — like most databases — make a distinction between no value (nil) and a neutral or default value like zero or a zero-length string. Attributes that are not optional must be set before the entity can be stored.

The Default Value is the value given to an attribute when a new instance of the entity is created. If left blank, the initial value will be absent (nil).

The Transient option is used for attributes that are not persistent (that is, they aren't saved). As long as the instance of the entity's object exists in memory, the attribute will hold its value, but when the entity is stored for later retrieval, transient properties are discarded.

Note

One reason for using transient attributes is to model data types that Core Data doesn't understand. A transient attribute can use the Undefined data type, allowing you to use any kind of value your application needs. In order to store the value, you also include one or more persistent attributes in the entity that the undefined type can be translated into. The persistent attributes store the value in the database, which can be converted back into the special type when needed. For example, you might define a custom class that represents a direction (course, azimuth, and velocity). Core Data does not know how to store your object, but your object can be easily converted into three Double attributes that Core Data can store. You can read more about non-standard attributes at http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CoreData/Articles/cdNSAttributes.html.

Relationships

Relationships connect an instance of an entity to some number of other entities, and put the "relational" in relational database. The Destination of a relationship is the kind of entity, or entities, the relationship contains. By definition, all entities stored by a relationship must be the same. The destination can be any other entity in the model, including abstract entities or even itself. Self-referential references are common. For instance, a Person entity might have children and parent relationships, both of which refer to other Person entities.

The Inverse menu chooses the relationship in the destination entity that refers back to, or reflexively includes, this entity. A Class entity with a students relationship holds references to all of the Student entities enrolled in a class. The Student entity has a classes relationship that lists all of the Class entities a student is enrolled in. The students and classes relationships are said to be inverses of each other. Any Class that refers to a Student will find itself in the list of classes that student is enrolled in. Data modeling does not create inverse relationships automatically. You must create both complementary relationships yourself. Setting the inverse relationship simplifies the data model diagram and highlights the symmetrical nature of the relationships, as described in the "Data Model Diagram" section later in this chapter.

Relationships come in two flavors: to-one and to-many. By default, a relationship is a to-one relationship, meaning that the relationship refers to exactly one other entity. A mother relationship in a Person entity would be a to-one relationship because every person has exactly one mother. (For the purposes of this discussion, I'll use nominal biological mothers and ignore boundary conditions such as adoption and cloning.) A to-many relationship stores a variable number of references to other entities. Define a to-many relationship by checking the To-Many Relationship option in the details pane, as shown in Figure 15-5. A children relationship in a Person entity would be a to-many relationship. A person could have none, one, or many children.

FIGURE 15-5

Figure 15-5. FIGURE 15-5

To-many relationships can be bounded, enforcing that the number of entities referred does not fall outside a set range. Use the Min Count and Max Count fields to set those bounds. An example would be a livingParents relationship, which would have a minimum count of 0 and a maximum count of 2.

Like attributes, relationships can be Optional. This means that the relationship may be empty. On a to-one relationship, it means that that the relationship might be nil — logically equivalent to a to-many relationship with a minimum count of 0 and a maximum count of 1.

Relationships also have a Delete Rule. The Delete Rule determines what happens when an entity is removed from a relationship — or even if it is allowed to be removed. The meanings of the different settings are explained in the Core Data Programming Guide.

Adding Fetched Properties

Fetched properties are like relationships in that they define a set of related entities. Unlike relationships, they don't store actual references to other entities. Instead, they define a predicate (such as a rule or criteria) that determines what entities are included. Like relationships, you must select a destination entity. To define the predicate to use, click the Edit Predicate button. The predicate builder uses the context of the destination entity, so don't forget to select the destination before editing the predicate. The textual version of the predicate you define is displayed in the Predicate field but is not directly editable there, as shown in Figure 15-6.

FIGURE 15-6

Figure 15-6. FIGURE 15-6

Adding Fetch Requests

Fetch requests are simply predefined predicates. Your application can use them in a variety of ways. When you add a fetch request, the property list display changes from displaying regular properties to listing only fetch requests. To switch between two, select the desired view using the disclosure triangle at the bottom of the properties list. See the "Data Model Browser" section for more details.

DATA MODELING INTERFACE

The interface used by data modeling is so similar to the interface used for class modeling that it would be a waste to reiterate it all here. Instead, this section just points out the differences between class modeling and data modeling. Before reading this section, you should familiarize yourself with the class modeling interface if you have not already done so. Class modeling was described in detail in Chapter 14.

Data Model Browser

The data model browser lists the entities on the left, the entity properties in the middle, and the details pane on the right, as shown in Figure 15-7.

FIGURE 15-7

Figure 15-7. FIGURE 15-7

Selecting an entity in the Entity list displays the properties or fetch requests in the center list. Selecting multiple entities displays a combined list of all properties of the selected entities. The use of the + and − buttons has already been described in the "Creating Properties" section.

The disclosure triangles below each list control how each list is displayed. Similar to the class browser, the Property list has the choices of Show All Properties, Show Attributes, Show Relationships, and Show Fetched Properties. These four choices let you display all standard property types, or limit the display to a single type. The fifth choice, Show Fetch Requests, shows only fetch requests. The formats of the fetch request and property lists are different and are mutually exclusive; the browser can't display both properties and fetch requests at the same time.

Details Pane Views

The details pane shows the settings for the selected entity or property. If you have multiple entities or properties selected at once, the details pane displays the settings common to all of those items, if there are any. Changing a setting that applies to all of the selected items alters all of those items at once.

The details pane has four different inspectors that you select using the small tabs in the upper-right corner of the pane. Figure 15-8 shows the four different panes. From left to right, they are General, User Info, Configurations, and Synchronization.

FIGURE 15-8

Figure 15-8. FIGURE 15-8

General Pane

The General pane displays the settings and controls for the selected entity or property. The format of this pane varies depending on what items are selected in the browser and even on what settings have been selected.

User Info Pane

The User Info pane lists the user info dictionary attached to an entity or property. The dictionary cannot be attached to fetch requests. A user info dictionary is simply a list of key-value strings associated with the entity or property. These values are stored in the data model and can be retrieved at run time by your application for whatever purpose you need.

Configurations Pane

Configurations are named collections of entities and apply only to entities. Using configurations, a data model can contain many different combinations of entities. Your application can then selectively load a data model that contains only the set of entities that it needs. For example, an application for professional photographers might have entities for Image, Thumbnail, Keyword, ModelRelease, CopyrightOwner, and PriceSchedule. That same application, running in "amateur" mode, might only want a data model that includes Image, Thumbnail, and Keyword. This can be accomplished in a single data model by creating two configurations, "Professional" and "Consumer," including all of the entities in the "Professional" configuration, but omitting ModelRelease, CopyrightOwner, and PriceSchedule from the "Consumer" configuration.

Create new configurations by clicking the + button at the bottom of the pane. Select one or more entities and put a check mark next to the configurations they belong in. To delete a configuration, select it and click the − button or press the Delete key.

You'll also find versioning, migration, and miscellaneous storage settings in the Configurations pane. The Renaming Identifier is described later in the "Migrating Data Schemas" section.

Synchronization Pane

The synchronization settings let you integrate your Core Data model with Sync Services, a framework for synchronizing data changes with remote data sets. You can learn more about Sync Services in the Sync Services Programming Guide included in the Xcode documentation, or online at http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/SyncServices/.

Data Model Diagram

Nodes in a data model represent entities. The compartments of entity nodes are Attributes, Relationships, and Fetched Properties (see Figure 15-9). The Fetched Properties compartment appears only if fetched properties have been defined for the entity. Fetch requests are not visible in the data model diagram.

FIGURE 15-9

Figure 15-9. FIGURE 15-9

Lines between entities describe relationships and inheritance. The shapes of the arrowheads indicate the kind of relationship, as listed in the following table:

LINE

RELATIONSHIP

Single arrowhead

To-one relationship. The arrow points to the destination entity.

Double arrowhead

To-many relationship. The arrow points to the destination entity.

Hollow arrowhead

Inheritance. The arrow points to the superentity.

If two relationships have been flagged as being inverse relationships, the data model diagram represents both relationships as a single line with two arrowheads. This greatly improves the readability of the diagram.

Inheritance and unidirectional relationship lines begin at their source and point abstractly toward the destination entity. For inverse relationships, Xcode draws the line precisely from one complementary relationship property to the other.

Selecting a node or attribute in the diagram selects the same in the browser. Selecting a relationship line in the diagram selects the relationship in the browser. If the line represents two inverse relationships, both relationships are selected along with the entities that contain them.

Like class modeling, you can customize the appearance of nodes. You cannot, however, filter the entities or properties that are displayed.

Tools

The Arrow, Magnifying Glass, and Hand tools work exactly as they do in class modeling. There is no Notes tool in data modeling.

Use the Line tool to create relationships between entities. Select the Line tool and drag from one entity to another. A new relationship is created in the entity where the drag began. To create a relationship between an entity and itself, click the entity. Follow the steps in the earlier "Relationships" section for editing its details.

Duplicating Entities and Properties

You can copy entities and properties to the clipboard. From there, you can paste them into the same or different data models. You can also duplicate entities in the data model diagram by holding down the Option key while dragging an entity to a new location. Duplicated entities are given names with numeric suffixes.

Predicate Builder

The predicate builder allows you to construct predicates — logical expressions used to find, select, or filter data — graphically. The predicate editor is based on the Cocoa Predicate framework. You can find both an introduction and a complete description of predicates in the Reference Library under Cocoa

Predicate Builder

You can invoke the predicate builder, shown in Figure 15-10, from a variety of places. It is even used in class modeling to determine what classes and members are displayed. The predicate builder is context-sensitive. That is, the pre-assembled set of values used to construct the expression will be garnered from the entity or its destination, as appropriate. Using the predicate builder will be easier and more productive if you have first defined the entire context surrounding the predicate before you begin editing it. Specifically, you should define the types of attributes and any relationships between entities before trying to edit a predicate.

FIGURE 15-10

Figure 15-10. FIGURE 15-10

Simple expressions are constructed from keys (which specify the variable or attribute), operators, and values. You can combine simple expressions using logical operators. The textual form of the expression in Figure 15-10 is year >= 3 AND (ANY classes.department.name == "Zoology" OR ANY classes.department.name == "Anthropology" OR ANY classes.department.name == "Biology") AND ANY classes.level > 400.

Simple Expressions

The key menu selects the attribute you want to compare. The keys listed will be the attributes of the entity that the predicate is being built for. To select something more complex than a simple attribute, choose the Select Key item from the menu. This presents a key browser, shown in Figure 15-11.

The browser shows the same attributes, but also includes the relationships defined for the entity. A Key Value path is like a directory path, in that it can specify an attribute of an entity related to an entity. For example, in the university data model, students are enrolled in a class. That class is taught by a faculty member, who belongs to a college. In a Class entity, the Key Value path instructor.department.name specifies the name of the department of the faculty member that teaches the class.

FIGURE 15-11

Figure 15-11. FIGURE 15-11

The type of the variable selected determines the operators and values that can be used in the expression. A Boolean variable presents only two operators (equals and does not equal) and two values (yes and no) to complete the expression. A string value presents numerous operators, all appropriate to matching string values. Select the desired operator from the operator menu.

The selected operator further refines the type of value that will be used in the comparison. Some operators, such as the "within" operator, have more than one value. Normally, the value is a constant. Type the value of the constant into the field. The text entered in the constant field must agree with the type of data on the other side of the operator. You cannot compare a Decimal attribute with the string "zero."

The value can also be another attribute (specified by a key) or a variable. Variables are values defined at run time and exist in an environment space associated with the predicate. Variables appear as $VAR_NAME in a predicate expression. To change the constant field to a variable or key, Right/Control-click in the background of the expression and select Constant, Variable, or Key from the contextual menu, as shown in Figure 15-12. This is usually easiest to do by Right/Control-clicking just to the immediate right of the value field. Some choices may be disabled, depending on the data type of the key or the operator selected.

FIGURE 15-12

Figure 15-12. FIGURE 15-12

To compare the key with a variable, enter the name of the variable in the Variable field. Variable names cannot be verified in the data model, so make sure they are spelled correctly. You select a key value just as you would a key on the left side of the expression.

Compound Expressions

You can combine simple expressions using logical operators to form compound expressions. Compound expressions are constructed by encompassing a simple expression, or expressions, within one of the logical operators: And, Or, or Not. In the cases of And and Or, the operator must encompass at least one other simple expression but can encompass more than two. The Not operator is unary and simply negates the expression it encloses.

You have two ways of inserting logical operators into a predicate expression. The + button at the right of every simple expression inserts a new simple expression. If the expression is not already enclosed in a logical operator, a new logical operator is inserted (AND, by default). If a logical operator already encloses the expression, that operator is expanded to include the new expression.

The Add AND, Add OR, Add NOT, and Add Criteria commands are located on every key, logical operator, and Right/Control-click menu in the predicate builder. Selecting Add Criteria is identical to clicking a + button. The other three commands insert a new logical operator enclosing the expression. When you insert a new AND or OR operator, a new simple expression is also created and inserted below the existing expression. Remember that AND and OR operators must enclose at least two expressions. Add Criteria creates a logical operator only when it has to. The other three — Add AND, Add OR, and Add NOT — always insert a new logical operator. You can change a logical operator from AND or OR and back again using its menu.

Drag expressions, even entire subexpression trees, to rearrange them in the tree. You can click and drag any portion of an expression's background, but it's often hard to miss the control areas of the pop-up menus. The most reliable drag point is the left end of the line that runs through the middle of the expression, or at the root of an expression tree.

Figure 15-13 shows an OR expression being dragged to a different location in the expression tree. Dragging does not create new logical operators. However, if a logical operator contains only two expressions, dragging one of them to another subexpression deletes it — just as if you had deleted the expression.

FIGURE 15-13

Figure 15-13. FIGURE 15-13

Use the − button to the right of the expression to delete it. You can delete expressions and NOT operators by choosing the Remove command from any of the expression menus. You cannot delete the logical operators AND and OR directly. To delete an AND or OR operator, delete or remove all but one of the expressions the operator encompasses.

Textual Expressions

Every expression constructed by the predicate builder has a textual representation as well. It is this textual version of the expression that you see in the data model browser. You can also enter predicate expressions directly, or you can insert textual subexpressions in the predicate builder. You may elect to do this because entering the expression directly is often easier than building one graphically, or because you want to use specialized functions or non-trivial Key Value paths.

To enter an expression directly, type the formula into the Predicate field, double-click the predicate expression in the fetched properties list, or add an Expression term in the predicate editor, as shown in Figure 15-14. The expression must be a valid predicate expression. As long as the predicate builder is open, the expression will be displayed just as you entered it.

Warning

Be careful when pasting expressions that the predicate builder can't represent. It is possible to enter expressions, or later change the definition of an entity, resulting in an expression that cannot be edited. If you find yourself in this situation, copy the textual representation of the expression in the browser, delete the fetched property or request, and then create a new one by pasting the (modified) expression into the Expression field.

FIGURE 15-14

Figure 15-14. FIGURE 15-14

When you close the predicate builder, the predicate is compiled and stored as archived Predicate objects in the data model. When you edit that predicate again, the predicate editor reinterprets the expression and creates a minimal representation of it. Consequently, the expression in the predicate builder may look different when you edit it again, but will logically represent the same statement. Figure 15-15 shows the expression previously shown in Figure 15-14, after it was saved and reopened.

FIGURE 15-15

Figure 15-15. FIGURE 15-15

CREATING AN INSTANT INTERFACE

Xcode can create an "instant interface" from a data model. An instant interface produces a functional Cocoa user interface that allows you to enter and edit data in your data model. This can be a huge time saver if you are just getting your application going or just need a minimal interface in which to view or edit your data. You often have some portion of a working data model, but no data and little or nothing that resembles an application.

To create an instant interface, you'll first need a window to put it in: open a nib document in Interface Builder that already has a Cocoa window in it, create a new nib document, or add a new window object to an existing nib document.

You can now initiate the interface builder process from either Xcode or Interface Builder:

  • To use Xcode, arrange the window so that it is visible on the screen alongside your data model window in Xcode. Switch back to the data model window. Select the Pointer tool. While holding down the Option key, click and drag an entity from the data model diagram and drop it into the Interface Builder window. When you start the drag, a shadow of the entity with a + sign follows the cursor. If it does not, you are not dragging a copy of the entity.

  • To use Interface Builder, drag a Core Data Entity object (you'll find it in the Core Data group) from the library palette into the window. A browser appears. Select the project, data model, and entity for which you want to generate the interface.

Xcode now asks you if you want an interface that represents one or many entity objects. Entry fields are created for each attribute. For a collection of entities, Fetch, Add, and Delete buttons can be created along with a table listing all of the instances in the collection. Figure 15-16 shows the instant interface created for many Faculty entities.

FIGURE 15-16

Figure 15-16. FIGURE 15-16

Amazingly, the entire interface is produced without any code. The interface is constructed using bindings and Interface Builder connections. If nothing else, it's a testament to the power of bindings. A little time spent exploring these bindings can be very educational.

MIGRATING DATA SCHEMAS

The problem with persistent data is that it is, well, persistent. Data written to a data file might exist for years, even decades, while your application continues to evolve. Unless your data model is trivial, or you have exceptional foresight, a time will come when you need to change your data model, such that the data model of your application no longer agrees with the structure of the existing database. When this happens, your application is no longer able to read the previously written data. The important thing to remember is this:

  • A data schema must agree with the format and organization of the information in the data store. Any discrepancies make the two inoperable.

To help circumvent this inevitable disaster, Core Data supports data model versioning. Instead of simply changing your data model, you create a new one — a version. When your new application attempts to access its old data, Core Data locates the previous data schema version, uses that to read the data, and then transitions the data from the old schema to the new one; a process called data migration.

Warning

Make backups of your existing (working) data stores before beginning any migration project. You probably won't get it right the first time, which might leave you with an incorrectly migrated data store.

This section describes the data modeling tools that let you configure versions, define mappings between versions, and provide hints for Core Data to use during migration. You should read the Core Data Model Versioning and Data Migration Programming Guide for more in-depth information, as well as the programmatic requirements.

Note

Core Data migration requires code to be added to your application; it won't happen automatically, even after you've followed all of the steps in this section. See the Core Data Model Versioning and Data Migration Programming Guide for the code needed to request or initiate a data migration.

Creating a New Version

The first step in any migration strategy is to create a new version of your data schema. With your current data model open, or selected in the source group, choose Design

Creating a New Version
FIGURE 15-17

Figure 15-17. FIGURE 15-17

If this is your first alternate version, your single data model document becomes a group containing the original and all subsequent versions. This parallels the organization of localized resources, which appear as a group containing their individual variants.

When compiled, all of the versions will be combined into a single managed object model (mom) bundle, providing your application with run time access to all versions. One version is designated as the current version, indicated by a check mark (see Figure 15-17). To change the current version, open or select the desired data model document and choose Design

FIGURE 15-17

Warning

Be careful not to build your data models twice. When you create the first new version of your data model, your mom document deployment changes from building a single .mom file to packaging multiple .mom files inside a nested .momd bundle. The .momd bundle is built by the data model (.xcdatamodeld) group that appears in your project. The data model source group should be included in the target — that group compiles all your versions and deploys a single bundle. The individual .xcdatamodel source files inside the group should not be included in the same target. If you include both, the application's bundle will contain duplicate data models — one inside the bundle and one outside. Core Data will (by default) attempt to merge these, and your application will abort with obscure errors like "Can't merge models with two different entities named 'User'." You may need to perform a Build

FIGURE 15-17

Each version has an arbitrary version identifier string that you assign. Open the data model, ensure that no entities or properties are selected, and choose the File

FIGURE 15-17
FIGURE 15-18

Figure 15-18. FIGURE 15-18

Now make the needed modifications to the newly created data schema version. The next two sections will help you configure the migration from the old schema to the new one.

Adding Lightweight Migration Hints

If you are using Mac OS X 10.6 or later, Core Data provides a lightweight migration strategy for data models with only superficial changes. If you are using an earlier version of Core Data, or if you make signification changes to your data model, you'll need to create a mapping, as described in the next section.

Lightweight migration has several advantages:

  • You don't have to create a migration mapping.

  • You might not have to version your data model.

  • It's fast.

Core Data can migrate from one schema to another as long as the changes are limited to:

  • Adding new attributes to an entity

  • Making a required attribute optional

  • Making an optional attribute required and providing a default value

  • Renaming an entity

  • Renaming an attribute

As long as your data schema changes are confined to this set of changes, you can use lightweight migration.

Renaming an entity or attribute requires you to create a data model version and provide Core Data with a little extra assistance. In the configurations inspector of the entity or attribute is a Renaming Identifier, as shown in Figure 15-19.

FIGURE 15-19

Figure 15-19. FIGURE 15-19

If you need to rename an entity or attribute, first create a new version of your data model. In the new version, rename the element and then set this field to the element's previous name. This tells Core Data which entity or attribute in the old schema corresponds to the new one.

Note

If you didn't rename any entities or attributes, you can use lightweight migration without creating a new version of your data schema. See the Data Migration Guide for the cases where you don't need to create a data schema version.

Creating a Migration Mapping

A migration mapping transitions the data from one schema to another. You can initiate a migration programmatically or let the Core Data framework perform it automatically, as needed. In either case, the migration proceeds in roughly the following phases:

  1. The versions of the old and new data schemas are identified and a suitable mapping is found.

  2. The old data store is opened and a new data store is created.

  3. Objects are created from the old data store.

  4. The mapping is used to create new objects in the new data store, and then insert copy/convert the old attributes into new attributes.

  5. The relationships between old objects are replicated in the new objects.

  6. The new objects are written to the new data store and the old objects are discarded.

The Core Data framework provides many of the basic tools needed to map one data schema to another, and most of these can be configured in Xcode's mapping model tool. If you need a more sophisticated translation, almost every phase of the migration can be influenced with custom code.

To create a data model mapping, follow these steps:

  1. Create a new data model version. You must have at least two versions to create a mapping.

  2. Choose the File

    Creating a Migration Mapping
  3. Select the Mapping Model template (in the Resource group).

  4. Give the mapping document a name. I tend to name my mappings after the model and the versions it translates between, such as Accounts2to3.xcmappingmodel.

    1. Choose the project to which to add the mapping document.

    2. The location for the new document should be inside your project's folder, preferably with the other data model documents.

    3. Choose the targets. Each target must also include both of the data models in question.

  5. Xcode presents a dialog box similar to the one shown in Figure 15-20.

    1. In your project's files, locate and select the old data model file. You will need to expand your data model group, because you have more than one version.

    2. Click the Set Source Model button.

    3. Select the new data model file.

    4. Click the Set Destination Model button.

Xcode constructs a new mapping model document, and fills in the translations that look obvious to it. Your job is to supply anything that's missing or needs to be customized.

FIGURE 15-20

Figure 15-20. FIGURE 15-20

The mapping model document window, shown in Figure 15-22, is organized very much like the data model browser: the left column lists the entity mappings. Selecting an entity mapping displays the property mappings for that entity mapping in the middle column. On the right is a details pane where you set the specifics for the selected entity or property mapping. You can add or remove mappings using the + and − buttons/menus at the bottom of the columns.

Note

Order is sometimes important. Entity mappings are performed in a specific order, and if you have mappings that depend on previous mappings then that order may be significant. To change the order, first sort the entity mappings column by order (the # column). You can then drag entity rows into the sequence you desire.

Most of the details concern how entities are mapped to other entities, and how the attributes of new objects are determined using the content of old objects.

One field of note is the Custom Policy property of an entity mapping. A custom migration policy object is a subclass of NSEntityMigrationPolicy. It receives a series of messages as the various migration phases are performed, allowing you to inject custom code into the process. Enter the name of your custom subclass of NSEntityMigrationPolicy into the field. Core Data creates an instance of your class and lets it participate in various aspects of entity migration.

An example serves as a good explanation. Let's say you have a Core Data project that is just getting started. You begin by defining a User entity that has firstName, lastName, location, and email attributes.

It's a good start, and you've entered some data into your database, but you quickly realize that location is vague and really should have been country, and the e-mail addresses should have been a separate table so you can relate multiple accounts that use the same e-mail address. You continue development, following these steps:

  1. Open the current data model. Make sure it's saved. Choose Design

    FIGURE 15-20
  2. Open the new data model (BigBrother_DataModel 2).

  3. Select the User entity mapping. Rename the location attribute to country.

  4. Remove the email attribute from User.

  5. Create a new Email entity.

  6. Add an emailAddress attribute to the new entity.

  7. Return to the User entity and add a new to-one relationship, name it email, and choose Email as the destination entity. Your model should now look like the one in Figure 15-21.

  8. Save the model and choose Design

    FIGURE 15-20
  9. Make whatever code and nib document changes are needed to work with your new data model.

    FIGURE 15-21

    Figure 15-21. FIGURE 15-21

At this point, you've created a new version of your data schema and application. Unfortunately, the data you already entered is in the old version and can no longer be read. Launching your application at this point would simply result in Core Data errors.

You construct a mapping to convert the data in the old schema into the new one:

  1. Choose File

    FIGURE 15-21

You then edit your migration mapping to fill in the missing pieces:

  1. Handle the attribute rename by selecting the UserToUser mapping, and then selecting the county attribute mapping. Set its value expression to $source.location. This copies the value of the location attribute from the old User to the country attribute in the new User.

  2. Select the Email entity mapping. Change the source from nothing to User. This now creates a new Email entity for every User entity in the old database. (There are now two mappings for the User entity, so every old User object is processed twice — once to make a new User and again to make a new Email.)

  3. Select the emailAddress attribute mapping of the new UserToEmail mapping. Enter a value expression of $source.email. This copies the email attribute of the old User entity into the emailAddress attribute of the new Email entity.

  4. Select The UserToUser entity mapping, and then select the email attribute mapping. Under the Auto Generate Value Expression, enter a KeyPath of $source and Mapping Name of UserToEmail, as shown in Figure 15-22. This sets the email relationship in the new User object to the single object created by the UserToEmail mapping (that is, the new Email object).

    FIGURE 15-22

    Figure 15-22. FIGURE 15-22

  5. Add the following code to your application's data store setup:

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES],
                             NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES],
                             NSInferMappingModelAutomaticallyOption,
                             nil];
  6. Pass the newly created options object in the -addPersistentStoreWithType:configuration:URL:options:error: message during startup.

Your application is now ready to run. When it starts, Core Data detects that your current data model does not agree with the structure of your existing store. It automatically finds the older version of the data schema that does, finds the mapping that maps between those two models, applies the mapping, and updates your data store.

CREATING NSMANAGEDOBJECT SUBCLASSES

The beginning of this chapter mentioned that the entities you define in your data model exist as instances of NSManagedObject at run time. Quite often, NSManagedObject is more than sufficient for your needs.

You might, however, need a class with more specialized functionality for many reasons:

  • Localized business logic in the entity object

  • Specialized or complex validation

  • Custom pre- or post-processing of attribute changes

  • Non-standard attribute types

There are also a number of reasons why you wouldn't need to subclass NSManagedObject:

  • Provide attribute property storage

  • Use any of the built-in Core Data property features

  • Inherit properties from other entities

  • Use Key Value Coding, Key Value Observing, or Bindings

An NSManagedObject reads a description of its entity and dynamically synthesizes real Objective-C accessor methods at run time. The result is an object that, for all intents and purposes, is identical to a custom class that defines those properties in code. As an example, an NSManagedObject for an entity that defines a string name attribute property would be functionally identical to a custom class that implemented accessors compatible with a @property NSString* name directive. You can send either object -name and -setName: messages, watch for changes using Key Value Observing, and so on. Ask yourself if you really need to subclass NSManagedObject before continuing.

To create a custom implementation class for an entity, start by subclassing NSManagedObject. You cannot use just any arbitrary class; your class must be a subclass of NSManagedObject. The easiest way to get started is to use the Managed Object Class file template. This template appears only when you have a data model open and (optionally) one or more entities selected prior to choosing the File

CREATING NSMANAGEDOBJECT SUBCLASSES

Unlike most other new file templates, the Managed Object Class does not ask for a filename. The filename is generated from the names of the entities. You do need to choose a location for the files and the targets in which the Objective-C source files should be included. When you are done, click the Next button, and Xcode presents the pane shown in Figure 15-23.

FIGURE 15-23

Figure 15-23. FIGURE 15-23

Check the entities for which you want to create custom implementation classes. By default, the entities that were selected in the model are already checked, but you are free to alter that here. You then need to pick any of the code generation options:

  • Generate Accessors

  • Generate Obj-C 2.0 Properties

  • Generate Validation Methods

The Generate Accessors option generates standard accessor methods for every property defined in the entity. These methods will be Key Value Coding, Key Value Observing, and Bindings compliant.

The Generate Obj-C 2.0 Properties option augments the Generate Accessors option by producing modern Objective-C 2.0 @property and @synthesize directives, instead of emitting boilerplate Objective-C code. This option does nothing if the Generate Accessors option isn't also selected.

The Generate Validation Methods define validation methods for every attribute defined. You can keep the ones you want and delete the ones you don't need to override.

Warning

As of this writing, the Generate Obj-C 2.0 Properties object mysteriously defeats the Generate Validation Methods option. If you want Xcode to produce stub methods for validating your properties, leave the Generate Obj-C 2.0 Properties option off. You can easily replace the simplistic getters and setters with @property and @synthesize directives later.

If the plan for your NSManagedObject subclass is to add special functionality, and not to customize the behavior of its properties, leave all of the code generation options unchecked. This generates empty subclasses of the selected entities.

Core Data accesses the properties of NSManagedObject using a set of heuristics. If an object has an accessor defined for an attribute, that accessor is called to obtain or set the value. If not, the value is obtained using the default value method implemented in NSManagedObject. Only define accessor and instance variables for special cases, and omit any code for properties you want handled normally by NSManagedObject. See the Subclassing Notes of the NSManagedObject documentation for more details.

After your custom implementation class is defined, edit the data model so that the new class name is specified in the Class field of the entity's details pane. Use the refactoring tool (Chapter 10) if you want to rename any of the generated class names first.

Note

The inheritance of your custom classes does not have to parallel the inheritance of your entities. For example, say your data model defines the entity Vegetable, which inherits from Crop, which inherits from Plant. You then create custom NSManagedObject subclasses, MyCrop and MyVegetable, and assign those to the Crop and Vegetable entities, respectively. Here's the surprising bit: MyVegetable isn't required to be a subclass of MyCrop. It might seem strange, and I can't imagine many practical uses, but it's perfectly valid from Core Data's perspective.

Of course, you don't have to use the Managed Object Class template. You are free to write your own subclass of NSManagedObject. It makes no difference to Xcode. Note that the accessor methods generated by Xcode include explicit calls to -willAccessValueforKey:, -didAccessValueForKey:, -willChangeValueForKey:, and so on. This is because NSManagedObjects disable the normal automatic messaging for Key Value Observing. You need to be mindful of this fact when implementing your own methods, which is another good reason to start with those generated by the template.

EXPORTING CLASS METHODS

The Managed Object Class template is a great time saver, but it's a one-shot tool. You can't modify your entity and use the template again. Doing so will either overwrite or replace the previous class files — a dialog box warns you that Xcode is about to replace the class files and asks what you want to do with the old ones. You either have to replicate your previous customizations or copy and paste from the old implementation.

If you are simply adding new properties to an entity, there's an easier way. Xcode will produce the same accessor methods that the template creates, but copy them to the clipboard instead. It will do this for any selected property, or properties, in the data browser. Select one or more properties and choose any of the following commands:

  • Design

    EXPORTING CLASS METHODS
  • Design

    EXPORTING CLASS METHODS
  • Design

    EXPORTING CLASS METHODS
  • Design

    EXPORTING CLASS METHODS

Switch to the implementation or header file for the class, as appropriate, and paste in the new methods.

IMPORTING DATA MODELS

To import the data model in a compiled .mom document, open a data model document and choose the Design

IMPORTING DATA MODELS

If you're trying to import a .mom document contained in an application bundle, it's a bit tricky because Xcode's open file dialog won't let you select files inside a package. To do that, first open the contents of the application package in the Finder by Right/Control-clicking and choosing the Show Package Contents command. In Xcode, choose Design

IMPORTING DATA MODELS

SUMMARY

Data modeling is a powerful tool. You can start incorporating the power of Core Data into your application in a matter of minutes, simply by creating an entity or two. Like class modeling, it is also a visualization tool, so you never lose sight of the "picture" of your data model throughout the course of your development. Instant interfaces make getting your application up and running quickly even easier. As your data schema evolves, versioning and migration tools will keep your data coming right along with you.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset