Building Data Models - Pro iOS Persistence: Using Core Data (2014)

Pro iOS Persistence: Using Core Data (2014)

Chapter 2. Building Data Models

You can create applications with the most intuitive user interfaces that perform tasks users can’t live without, but if you don’t model your data correctly, your applications will become difficult to maintain, will underperform, and might even become unusable. This chapter explains how to model your data to support, not undermine, your applications.

In the previous chapter, you’ve built a simple iOS application using Xcode’s Empty Application template. In this chapter, we model a bookstore and build an application to store information about the books we have. As in the previous chapter, we don’t build any user interface code yet, maintaining the focus on Core Data.

Designing Your Database

The American philosopher Ralph Waldo Emerson once said, “A foolish consistency is the hobgoblin of little minds.” People often wield that quote to defend carelessness or inattention to detail. We hope we’re not falling into that trap as we flip-flop inconsistently between claiming Core Data is not a database and treating it as if it were. This section discusses how to design your data structures, and likening this process to modeling a relational database greatly helps not only the discussion but also the resulting design. The analogy breaks down in spots, however, as all analogies do, and we point out those spots throughout this discussion.

Core Data entity descriptions look, act, smell, and taste an awful lot like tables in a relational database. Attributes seem like columns in the tables, relationships feel like joins on primary and foreign keys, and entities appear like rows. If your model sits on a SQLite persistent store, Core Data actually realizes the entity descriptions, attributes, relationships, and entities as the database structures you’d expect. Remember, though, that your data can live in memory only or in some flat-file atomic store that has no tables or rows or columns or primary and foreign keys. Core Data abstracts the data structure from a relational database structure, simplifying both how you model and how you interact with your data. Allow for that abstraction in your mental model of how Core Data modeling works or you will fill your Core Data model with cruft, work against Core Data, and produce suboptimal data structures.

Newcomers to Core Data who have data modeling experience decry the lack of an autoincrement field type, believing that they have the responsibility, as they do in traditional data modeling, to define a primary key for each table. Core Data has no autoincrement type because you don’t define any primary keys. Core Data assumes the responsibility to establish and maintain the uniqueness of each managed object, whether it’s a row in a table or not.

Having no primary keys means having no foreign keys either; Core Data manages the relationships between entities, or tables, and performs any necessary joins for you. Get over the discomfort of not defining primary and foreign keys quickly, because agonizing over this implementation detail isn’t worth your effort or angst.

Tip Here’s a rule of thumb to remember as you create your Core Data models: worry about data, not data storage mechanisms.

Relational Database Normalization

Relational database theory espouses a process called normalization that’s used for designing a data model. Normalization aims to reduce or eliminate redundancy and provide efficient access to the data inside the database. The normalization process divides into five levels, or forms, and work continues on a sixth form. The five normal forms carry definitions only a logician can love, or perhaps even understand; they read something like the following:

A relation R is in fourth normal form (4NF) if and only if, wherever there exists an MVD in R, say A -> -> B, then all attributes of R are also functionally dependent on A. In other words, the only dependencies (FDs or MVDs) in R are of the form K -> X (i.e. a functional dependency from a candidate key K to some other attribute X). Equivalently: R is in 4NF if it is in BCNF and all MVD’s in R are in fact FDs.

(www.databasedesign-resource.com/normal-forms.html)

We have neither sufficient pages in this book nor the inclination to walk through formal definitions for each of the normal forms and then explain what they mean. Instead, this section describes each of the normal forms in relation to Core Data and provides data modeling advice. Note that adherence to a given normal form requires adherence to all the normal forms that precede it.

A database that conforms to first normal form (1NF) is considered normalized. To conform to this level of normalization, each row in the database must have the same number of fields. For a Core Data model, this means that each managed object should have the same number of attributes. Since Core Data doesn’t allow you to create variable numbers of attributes in your entities, your Core Data models are automatically normalized.

Second normal form (2NF) and third normal form (3NF) deal with the relationships between non-key fields and key fields, dictating that non-key fields should be facts about the entire key field to which they belong. Since your Core Data model has no key fields, you shouldn’t run afoul of these concerns. You should, however, make sure that all the attributes for a given entity describe that entity, and not something else. For example, a Category entity that describes a category for books shouldn’t have an author field since the author describes a book, not the category the book belongs to.

The next two normal forms, fourth normal form (4NF) and fifth normal form (5NF), can be considered the same form in the Core Data world. They deal with reducing or eliminating data redundancy, pushing you to move multiple-value attributes into separate entities and create to-many relationships between the entities. In Core Data terms, 4NF says that entity attributes shouldn’t be used to store multiple values. Instead, you should move these attributes to a separate entity and create a to-many relationship between the entities. Consider, for example, the categories and books in the model in the bookstore application we build in this chapter. A model that would violate 4NF and 5NF would have a single entity, Category, with an additional attribute, book. We would then create a new category managed object for each book, so that we’d have several redundant Category objects. Instead, the bookstore data model will include a Category entity and separate Book entities.

Building a Simple Model

Create a new single view application project in Xcode so we can start building a simple model. Pursuing our imaginative app naming series, name this new application BookStore and set the language to your choice as shown in Figure 2-1.

image

Figure 2-1. Creating the BookStore project

At this time, you should already be familiar with all the Core Data components described in Chapter 1. Find the object model (named BookStore.xcdatamodeld) Xcode has created and open it. As expected, it is empty.

Tip In Chapter 1, we showed you how to keep your application delegate clean of Core Data initialization and move this over to a class like Persistence. It is your choice to replicate this pattern in this chapter or keep the Xcode template as is and continue.

Entities

Just as you would when designing an object-oriented application, you should take a moment to think about the level of details your Core Data model requires in order to design the right set of model objects. In the case of modeling a book library, we want to quickly be able to locate books within the library by category. So, we start our model with a BookCategory entity. Click the Add Entity button at the bottom of the Xcode window and rename the newly created entity to BookCategory.

Tip There is already a class called Category in the Objective-C runtime, so it is best to avoid using this name to prevent compilation errors. In this book, we use the name BookCategory instead.

Core Data represents objects in the runtime as instances of NSManagedObject. Select the BookCategory entity and open the Data Model inspector. Note how the BookCategory entity is mapped to the class NSManagedObject by default. While designing your models, keep in mind that entities are akin to classes (hence the similar naming convention).

Attribues

Attributes are the properties used to describe an entity. Imagine what would be necessary to describe the category for the purpose of the BookStore application. We would want to at least give the category a name so that librarians can quickly know what the category refers to. To add a name, add a new attribute called name to the BookCategory entity and change its type to String.

Tip Take note of how entity names use Pascal case, also known as upper camel case (similar to Objective-C class names), while attributes use a lowercase first letter but are also camel case (like Objective-C class properties).

At this point, your model should look like Figure 2-2.

image

Figure 2-2. The BookCategory entity with a name attribute

Core Data supports a limited set of attributes types as described in Table 2-1 (for Objective-C code) and Table 2-2 (for Swift code).

Table 2-1. Available Attribute Types in Objective-C

image

Table 2-2. Available Attribute Types in Swift

image

Most of these types are straightforward because they map directly to Objective-C data types. Using them is just a matter of leveraging the key/value accessors of the NSManagedObject instances. The Transformable type is the only type that doesn’t have an Objective-C counterpart. It is used for custom types, which you use for any type not already supported by Core Data. Use Transformable as the type when the attribute you’re trying to persist doesn’t fit neatly into one of the supported data types. For example, if your managed object needs to represent a color and needs to persist it, it would make sense to use the CGColorRef type. In this case you need to set the Core Data type to transient and Transformable and provide Core Data with the mechanism for transforming the attribute into supported data types. Custom attributes make sense to use when you create your own managed objects that extend from NSManagedObject, and we talk about them in more detail in Chapter 7.

Key-Value Coding

NSManagedObject instances are key-value coding (KVC) compliant. KVC is a mechanism for accessing the properties of an object using a predefined naming convention. NSManagedObject provides implementations for KVC methods, both for Objective-C and for Swift. Here are the method declarations in Objective-C:

- (void)setValue:(id)value forKey:(NSString *)key

and

- (id)valueForKey:(NSString *)key

Here are their Swift counterparts:

func setValue(_ value: AnyObject!, forKey key: String!)

and

func valueForKey(_ key: String!) -> AnyObject!

Accessing an attribute in your code becomes a trivial matter, as this Objective-C snippet shows:

NSManagedObject *myCategory = ....
// Get the current value for the name attribute
NSString *name = [myCategory valueForKey:@"name"];
// Set the value for the name attribute to Magazines
[myCategory setValue:@"Magazines" forKey:@"name"];

Here is the Swift counterpart to that code:

var myCategory = ....
// Get the current value for the name attribute
var name: AnyObject! = myCategory.valueForKey("name")
// Set the value for the name attribute to Magazines
myCategory.setValue("Magazines", forKey: "name")

Generating Classes

Your code can always use NSManagedObject instances and KVC, but as your model grows and you have many different entities, it will inevitably become very confusing to call everything a managed object. For this reason, Core Data allows you to map entities directly to custom classes. All their attributes are mapped to Objective-C properties and everything becomes a lot simpler to manage.

In Xcode, select the BookCategory entity. In the menu, select Editor image Create NSManagedObject Subclass… and follow the prompts until you click Create.

Tip If you check the Use scalar properties for primitive data types checkbox, the code generator will use primitive types instead of NSNumber to store numeric values. When deciding whether to use scalar values or not, consider how you are going to access the data. For example, scalar values are easier to access and use in arithmetic operations, but they cannot be added to a collection without being wrapped into an NSNumber.

A new class called BookCategory has been added to your Xcode project. As expected for the Objective-C version, BookCategory.h looks like Listing 2-1.

Listing 2-1. BookCategory.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface BookCategory : NSManagedObject

@property (nonatomic, retain) NSString * name;

@end

If you’re creating this project in Swift, you’ll instead have a file called BookCategory.swift, shown in Listing 2-2.

Listing 2-2. BookCategory.swift

import Foundation
import CoreData

class BookCategory: NSManagedObject {

@NSManaged var name: String

}

In either language, BookCategory is a subclass of NSManagedObject and has a simple property of type NSString (Objective-C) or String (Swift) called name. This matches the model. The mapping between this class and the entity is kept in the model itself. Go back to the model and select the BookCategory entity. Open the Data Model inspector and see that its associated class is now BookCategory (the newly created class).

If you’re doing this project in Swift, you have a little more work to do, unless some future version of Xcode corrects this issue. Unlike Objective-C, Swift has namespaces. When Swift compiles your classes, it puts them into the namespace of their containing modules, which usually matches the project’s name. This means that your BookCategory class is really named BookStoreSwift.BookCategory (if you named your project BookStoreSwift as we did). The Core Data model, however, just shows BookCategory for the class. If you run the program now, you’ll get an error message in the console that looks something like:

CoreData: warning: Unable to load class named 'BookCategory' for entity 'BookCategory'. Class not found, using default NSManagedObject instead.

To correct this issue, you can either update the name of the class in the Core Data model to BookStoreSwift.BookCategory, or add an attribute to the BookCategory class to tell Core Data that the class really is named BookCategory after all, as Listing 2-3 shows.

Listing 2-3. Using the @objc attribute to Rename the Class for Objective-C

import Foundation
import CoreData

@objc(BookCategory)
class BookCategory: NSManagedObject {

@NSManaged var name: String

}

The @objc(name) attribute changes the class’s name, as far as any Objective-C classes are concerned, to name, so we can specify the un-namespaced version that our model specifies.

Accessing the name attributes is now even simpler and more familiar than before:

BookCategory *myCategory = ....
// Get the current value for the name attribute
NSString *name = myCategory.name;
// Set the value for the name attribute to Magazines
myCategory.name = @"Magazines";

In Swift, it looks like the following:

var myCategory = ....
// Get the current value for the name attribute
var name: AnyObject! = myCategory.name
// Set the value for the name attribute to Magazines
myCategory.name = "Magazines"

Relationships

As you normalize your data model, you’ll likely create several entities, depending on the complexity of the data you’re modeling. Relationships allow you to tie entities together. Core Data allows you to tune the relationships you create to accurately reflect how your data relate to each other. Start with adding a Book entity to your model. At this point, leave it with no attributes until we figure out what needs to go there.

In the BookStore application, you can see an obvious relationship between a category and the books it contains. For now, we will keep the problem reasonably simple by stating that a book can belong to only one category. Let’s reflect this relationship in the model.

Select the BookCategory entity and in the Relationships section, click the + button. Xcode creates a new relationship for you to configure. Enter books for the name, and select Book as the destination, and leave the inverse blank for now. In the Data Model inspector tab in Xcode, change the type to To Many to indicate that this relationship will point to possibly multiple books. This is called a one-to-many relationship. Figure 2-3 shows what the model looks like so far.

image

Figure 2-3. A one-to-many relationship

Leaving the Inverse field blank gives you compiler warnings. You can ignore these warnings and run your application without specifying an inverse relationship—after all, these are compiler warnings, not compiler errors—but you could face unexpected consequences. Core Data uses bidirectional relationship information to maintain the consistency of the object graph (hence the Consistency Error) and to manage undo and redo information. If you leave a relationship without an inverse, you imply that you will take care of the object graph consistency and undo/redo management. The Apple documentation strongly discourages this, however, especially in the case of a To-Many relationship. When you don’t specify an inverse relationship, the managed object at the other end of the relationship isn’t marked as changed when the managed object at this end of the relationship changes. Consider, for example, if a player object is deleted and it has no inverse relationship back to its team. Any optionality rules or delete rules aren’t enforced when the object graph is saved.

In order to comply and help Core Data, add the inverse relationship—that is, for a given book, the category that it belongs to. Select the Book entity and add a new relationship. Set the name to category, the destination to BookCategory, and the inverse to books. If you go back to theBookCategory entity and select the books relationship, you will see that its inverse is now automatically set as well.

Relationship Properties

Let’s take a look at the options available for configuring relationships. Select the books relationship and open the Data Model inspector as shown in Figure 2-3.

The first field, Name, becomes the name of the key NSManagedObject uses to reference the relationship. By convention, it’s the name of the referenced entity in lower case. In the case of a To Many relationship, the name field conventionally is pluralized. Note that these guidelines are conventions only, but you will make your data models more understandable, maintainable, and easier to work with if you follow them. You can see that the BookStore data model follows these conventions—in the Book entity, the relationship to the BookCategory entity is calledcategory. In the BookCategory entity, the relationship to the Book entity, a To Many relationship, is called books.

The Properties field has two options, both checkboxes, which you can set independently of each other. The first, Transient, allows you to specify that a relationship should not be saved to the persistent store. A transient relationship still has support for undo and redo operations, but it disappears when the application exits, giving the relationship the lifespan of a Hollywood marriage. Some possible uses for transient relationships include the following:

· Relationships between entities are temporal and shouldn’t survive beyond the current run of the application.

· Relationship information can be derived from some external source of data, whether another Core Data persistent store or some other source of information.

In most cases, you’ll want your relationships to be persistent and you’ll leave the Transient checkbox unchecked.

The second property checkbox, Optional, specifies whether this relationship requires a non-nil value. Think of this like a nullable vs. non-nullable column in a database. If you check the Optional checkbox, a managed object of this entity type can be saved to the persistent store without having anything specified for this relationship. If unchecked, however, saving the context will fail. If the Book entity in the data model for the BookStore application, for example, left this checkbox unchecked, every book entity would have to belong to a category. Setting the “category” value for a book to nil (or never setting it at all) would cause all calls to save: to fail with the following error description, “category is a required value.”

We cover the next field, Delete Rule, in the section “Delete Rule.”

The Type field has two options: To Many and To One. To One means that this relationship has exactly one managed object (or zero, depending on optionality settings) on each end of the relationship. For the BookStore application, making the books relationship on the Category entity a To One type would mean that only one book could exist in a given category, which would obviously be counterintuitive for a book category. If the entities were Frame and Picture, however, a To One type would make sense, as a frame typically has only one picture in it. Setting this field to To Many means the destination entity can have many managed objects that relate to each managed object instance of this entity type.

The next field, Count, sets limits on the number of managed objects of the destination entity type that can relate to each managed object of this entity type, and has effect only if the type is set to To Many. You can specify values for Minimum and/or Maximum, and Core Data will enforce the rules you set. Note that you can set one without setting the other—you don’t have to set both. Exceeding the limits you set causes the save: operation to fail with a “Too many items” or a “Too few items” error message. Note that the Optional setting overrides the Minimum Count setting: if Optional is checked and Minimum Count is 1, saving the context when the relationship count is zero succeeds.

Delete Rule

The Delete Rule setting allows you to specify what happens if you try to delete a managed object of this entity type. Table 2-3 lists the four possibilities and what they mean.

Table 2-3. Delete Rule Options

Rule name

Description

Cascade

The source object is deleted and any related destination objects are also deleted.

Deny

If the source object is related to any destination objects, the deletion request fails and nothing is deleted.

Nullify

The source object is deleted and any related destination objects have their inverse relationships set to nil.

No Action

The source object is deleted and nothing changes in any related destination objects.

Be careful how you set the Delete Rule for your relationships. In the BookStore application, for example, if you set the Delete Rule for the books relationship in the BookCategory entity to “Cascade,” deleting a category would delete all the books related to it. This is probably not what you want. If you set that rule to “Deny” and a category had any books in it, trying to delete the category would result in an error on any attempts to save the context with the message “category is not valid.” Setting the Delete Rule to “Nullify” would preserve the books in the persistent store, though they would no longer belong to any category.

The “No Action” option represents another way, like not specifying an inverse relationship, that Core Data allows you to accept responsibility for managing object graph consistency. If you specify this value for the Delete Rule, Core Data allows you to delete the source object but pretends to the destination objects that the source object still exists. For the BookStore application, this would mean removing a category but still telling customers that the books they are looking for are available, but they are not in any category for the customers to browse. You won’t find many compelling reasons to use the “No Action” setting, except perhaps for performance reasons when you have many destination objects. Chapter 9 discusses performance tuning with Core Data.

Ordered Properties

The Arrangement field specifies whether Core Data should maintain the order in which entities are added to the relationship, and applies only to a To Many relationship. To maintain the order, you check the Ordered checkbox. In the case of the BookStore application, we don’t care about the order in which books are added to a category, so to see an example of an ordered relationship, create a Page entity and create a To Many relationship called pages from Book to Page. Obviously in the case of pages, we want to maintain the order, so set the Arrangement field of the pagesrelationship to Ordered by checking the checkbox in the relationship properties. Don’t forget to set the inverse relationship.

The model should look as shown in Figure 2-4.

image

Figure 2-4. The BookStore model with three entities

Refresh the custom objects by selecting all three entities in the model (click the first one and shift+click on the last one) and, in the Xcode menu, select Editor image Create NSManagedObject Subclass… then follow the prompts until you can click Create. Xcode will ask you for permission to replace the existing BookCategory class. Click Replace and you should have three custom Core Data managed objects: BookCategory, Book, and Page. If you have chosen to generate these classes in Swift, don’t forget to either update the class names in the Core Data model to include the namespace (e.g., BookStoreSwift.Book), or to add the @objc() attribute to the class (e.g., @objc(Book)).

Open BookCategory.h and note how the unordered relationship to books is represented as an NSSet.

@property (nonatomic, retain) NSSet *books;

Now open Book.h and see how the ordered relationship is represented with an NSOrderedSet.

@property (nonatomic, retain) NSOrderedSet *pages;

You can see this in Swift by first looking in BookCategory.swift to see the NSSet:

@NSManaged var books: NSSet

And then looking at the NSOrderedSet in Book.swift:

@NSManaged var pages: NSOrderedSet

The NSOrderedSet class provides accessors that work with indices, allowing you to specify a strict order. Listing 2-4 shows the Objective-C method declarations, and Listing 2-5 shows the Swift declarations.

Listing 2-4. NSOrderedSet accessors in Objective-C

- (id)objectAtIndex:(NSUInteger)idx;
- (NSUInteger)indexOfObject:(id)object;

Listing 2-5. NSOrderedSet accessors in Swift

func objectAtIndex(_ index: Int) -> AnyObject!
func indexOfObject(_ object: AnyObject!) -> Int

You’ll also notice, in the Swift version, the @NSManaged attribute in front of both the books and the pages variables. The Objective-C implementation file BookCategory.m, shown in Listing 2-6, has an attribute called @dynamic in front of the name and books properties. Both these attributes (@NSManaged and @dynamic) do the same thing: they tell the compiler that the getter and setter for that property will be provided at runtime, not compile time.

Listing 2-6. BookCategory.m

#import "BookCategory.h"
#import "Book.h"

@implementation BookCategory

@dynamic name;
@dynamic books;

@end

Storing and Retrieving Data

Now that we’ve created a basic data model, let’s add some attributes to the entities we’ve created so that we can experiment with storing and retrieving data.

Select the Page entity and add a new text attribute of type String. We will use this to store the text on a given page. Select the Book entity and add a title attribute of type String and a price of type Float. Once done, regenerate all the custom Core Data objects for all three entities. Since we have numeric types and we prefer to access them through primitive types, be sure to check the Use scalar properties for primitive data types checkbox.

At this point, your project and model should look as illustrated in Figure 2-5.

image

Figure 2-5. The BookStore model and generated custom classes

Listing 2-7 shows the Book.h header file, and Listing 2-8 shows the Book.swift file.

Listing 2-7. Book.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class BookCategory, Page;

@interface Book : NSManagedObject

@property (nonatomic, retain) NSString * title;
@property (nonatomic) float price;
@property (nonatomic, retain) BookCategory *category;
@property (nonatomic, retain) NSOrderedSet *pages;
@end

@interface Book (CoreDataGeneratedAccessors)

- (void)insertObject:(Page *)value inPagesAtIndex:(NSUInteger)idx;
- (void)removeObjectFromPagesAtIndex:(NSUInteger)idx;
- (void)insertPages:(NSArray *)value atIndexes:(NSIndexSet *)indexes;
- (void)removePagesAtIndexes:(NSIndexSet *)indexes;
- (void)replaceObjectInPagesAtIndex:(NSUInteger)idx withObject:(Page *)value;
- (void)replacePagesAtIndexes:(NSIndexSet *)indexes withPages:(NSArray *)values;
- (void)addPagesObject:(Page *)value;
- (void)removePagesObject:(Page *)value;
- (void)addPages:(NSOrderedSet *)values;
- (void)removePages:(NSOrderedSet *)values;
@end

Listing 2-8. Book.swift

import Foundation
import CoreData

class Book: NSManagedObject {

@NSManaged var title: String
@NSManaged var price: Float
@NSManaged var category: BookCategory
@NSManaged var pages: NSOrderedSet

}

Notice in the Objective-c version how Core Data has automatically added the proper collection accessors, making the use of our objects and their properties easy. The Swift version, however, can’t generate corresponding runtime accessors, so they’re not present.

Inserting New Managed Objects

With our Core Data model fleshed out, we’re ready to put objects into it. Upon startup, we want to populate our Core Data store with some categories and books. For now, we will use hard-coded data. Open AppDelegate.m (Objective-C) or AppDelegate.swift (Swift) and edit theapplication:didFinishLaunchingWithOptions: method to invoke an initStore method, as shown in Listing 2-9 (Objective-C) or Listing 2-10 (Swift).

Listing 2-9. Creating a Hook to Preload Data (Objective-C)

- (void)initStore {
// Load data here
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self initStore];
return YES;
}

Listing 2-10. Creating a Hook to Preload Data (Swift)

func initStore() {
// Load data here
}

func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
initStore()
return true
}

Now all we have to do is create our objects and push them into the persistent store. Edit the AppDelegate.m or AppDelegate.swift file so that the initStore method looks like Listing 2-11 (Objective-C) or Listing 2-12 (Swift). For the Objective-C version, you also must add the following import statements:

#import "BookCategory.h"
#import "Book.h"
#import "Page.h"

Also worth noting is that the Objective-C version takes advantage of the dynamically-generated addBooks: method for adding books. Since Swift doesn’t have that support, we instead must retrieve the mutable set of books using NSManagedObject’smutableSetValueForKeyPath function, and then add the books to the NSManagedSet object that it returns.

Listing 2-11. The Objective-C initStore Method for Adding New Objects

- (void)initStore {
BookCategory *fiction = [NSEntityDescription insertNewObjectForEntityForName:@"BookCategory" inManagedObjectContext:self.managedObjectContext];
fiction.name = @"Fiction";

BookCategory *biography = [NSEntityDescription insertNewObjectForEntityForName:@"BookCategory" inManagedObjectContext:self.managedObjectContext];
biography.name = @"Biography";

Book *book1 = [NSEntityDescription insertNewObjectForEntityForName:@"Book" inManagedObjectContext:self.managedObjectContext];
book1.title = @"The first book";
book1.price = 10;

Book *book2 = [NSEntityDescription insertNewObjectForEntityForName:@"Book" inManagedObjectContext:self.managedObjectContext];
book2.title = @"The second book";
book2.price = 15;

Book *book3 = [NSEntityDescription insertNewObjectForEntityForName:@"Book" inManagedObjectContext:self.managedObjectContext];
book3.title = @"The third book";
book3.price = 10;

[fiction addBooks:[NSSet setWithArray:@[book1, book2]]];
[biography addBooks:[NSSet setWithArray:@[book3]]];

[self.managedObjectContext save:nil];
}

Listing 2-12. The Swift initStore function;. for Adding New Objects

func initStore() {
var fiction = NSEntityDescription.insertNewObjectForEntityForName("BookCategory", inManagedObjectContext: self.managedObjectContext) as BookCategory
fiction.name = "Fiction"

var biography = NSEntityDescription.insertNewObjectForEntityForName("BookCategory", inManagedObjectContext: self.managedObjectContext) as BookCategory
biography.name = "Biography"

var book1 = NSEntityDescription.insertNewObjectForEntityForName("Book", inManagedObjectContext: self.managedObjectContext) as Book
book1.title = "The first book"
book1.price = 10

var book2 = NSEntityDescription.insertNewObjectForEntityForName("Book", inManagedObjectContext: self.managedObjectContext) as Book
book2.title = "The second book"
book2.price = 15

var book3 = NSEntityDescription.insertNewObjectForEntityForName("Book", inManagedObjectContext: self.managedObjectContext) as Book
book3.title = "The third book"
book3.price = 10

var fictionRelation = fiction.mutableSetValueForKeyPath("books")
fictionRelation.addObject(book1)
fictionRelation.addObject(book2)

var biographyRelation = biography.mutableSetValueForKeyPath("books")
biographyRelation.addObject(book3)

self.saveContext()
}

The code is easy to read. Note, however, that you can’t allocate a managed object directly. Instead, you must obtain it from the managed object context. That is, of course, so the context can manage it.

The initStore method creates two categories and three books. It adds the two first books to the “fiction” category and the third book to the “biography” category. We purposely overlooked the Page entity here to not overcrowd the example.

Launch the app to execute this code. Once you get the white screen on the simulator, you can quit. In Chapter 1 we showed you how to find and view the SQLite store that backs your data store. Open the database file, BookStore.sqlite, with sqlite3 in a Terminal window.

The .schema command confirms that our data model is correctly created.

sqlite> .schema
CREATE TABLE ZBOOK ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZCATEGORY INTEGER, ZPRICE FLOAT, ZTITLE VARCHAR);
CREATE TABLE ZBOOKCATEGORY ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZNAME VARCHAR);
CREATE TABLE ZPAGE ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZBOOK INTEGER, Z_FOK_BOOK INTEGER, ZTEXT VARCHAR);
CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB);
CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER INTEGER, Z_MAX INTEGER);
CREATE INDEX ZBOOK_ZCATEGORY_INDEX ON ZBOOK (ZCATEGORY);
CREATE INDEX ZPAGE_ZBOOK_INDEX ON ZPAGE (ZBOOK);

And a couple of queries show that our data have been loaded:

sqlite> .header on
sqlite> .mode column
sqlite> select * from ZBOOKCATEGORY;
Z_PK Z_ENT Z_OPT ZNAME
---------- ---------- ---------- ----------
1 2 1 Biography
2 2 1 Fiction
sqlite> select * from ZBOOK;
Z_PK Z_ENT Z_OPT ZCATEGORY ZPRICE ZTITLE
---------- ---------- ---------- ---------- ---------- --------------
1 1 1 1 10.0 The third book
2 1 1 2 10.0 The first book
3 1 1 2 15.0 The second boo

Of course, we if run the application once again, new entities will be loaded and we will have duplicates.

sqlite> select * from ZBOOKCATEGORY;
Z_PK Z_ENT Z_OPT ZNAME
---------- ---------- ---------- ----------
1 2 1 Fiction
2 2 1 Biography
3 2 1 Biography
4 2 1 Fiction

This is because the initStore method assumes (wrongfully) that the store is empty when it runs. We could, of course, create a new persistent store upon startup by deleting the BookStore.sqlite file, but this might be a little overdone. There may be information from previous sessions we might want to use in the future. For safety, we will simply clear the entities we care about.

First let’s create a utility method, called deleteAllObjects, for clearing entities by entity name. You can find the Objective-C code for this method in Listing 2-13 and the Swift code in Listing 2-14. You pass this method the name of the entity for which you wish to delete all objects. This method fetches all objects for that entity and then deletes them.

Listing 2-13. The Objective-C deleteAllObjects Method

- (void)deleteAllObjects:(NSString *)entityName {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];

NSError *error;
NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

for (NSManagedObject *managedObject in items) {
[self.managedObjectContext deleteObject:managedObject];
}

if (![self.managedObjectContext save:&error]) {
NSLog(@"Error deleting %@ - error:%@", entityName, error);
}
}

Listing 2-14. The Swift deleteAllObjects Function

func deleteAllObjects(entityName: String) {
let fetchRequest = NSFetchRequest(entityName: entityName)

let objects = self.managedObjectContext?.executeFetchRequest(fetchRequest, error: nil)
for object in objects as [NSManagedObject] {
self.managedObjectContext?.deleteObject(object)
}

var error: NSError? = nil
if !self.managedObjectContext!.save(&error) {
println("Error deleting \(entityName) - error:\(error)")
}
}

Edit the initStore method to clear all books and categories before running by adding the following two lines at the beginning of the Objective-C method:

- (void)initStore {
[self deleteAllObjects:@"Book"];
[self deleteAllObjects:@"BookCategory"];
...
}

Your Swift function should look like this:

func initStore() {
deleteAllObjects("Book")
deleteAllObjects("BookCategory")
...
}

Now run the application once again and see how the store is clean no matter how many times we run it.

sqlite> select * from ZBOOKCATEGORY;
Z_PK Z_ENT Z_OPT ZNAME
---------- ---------- ---------- ----------
3 2 1 Fiction
4 2 1 Biography
sqlite> select * from ZBOOK;
Z_PK Z_ENT Z_OPT ZCATEGORY ZPRICE ZTITLE
---------- ---------- ---------- ---------- ---------- --------------
4 1 1 3 10.0 The first book
5 1 1 3 15.0 The second boo
6 1 1 4 10.0 The third book

You might notice that the primary key counter doesn’t reset (the Z_PK column in the tables)—it continues to increment as you add and delete objects. Remember that this is an implementation detail, so you can ignore it. Let Core Data handle it.

Retrieving Managed Objects

We’ve briefly touched, through code samples, on how to retrieve objects from the persistent store. Let’s take a closer look at how to pull information. For this, create a new method called showExampleData. Edit application:didFinishLaunchingWithOptions: to add a call to this new method right after calling initStore, as Listing 2-15 (Objective-C) and Listing 2-16 (Swift) show.

Listing 2-15. Calling showExampleData (Objective-C)

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self initStore];
[self showExampleData];
return YES;
}

Listing 2-16. Calling showExampleData (Swift)

func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
initStore()
showExampleData()
return true
}

Now add the showExampleData method shown in Listing 2-17 (Objective-C) and Listing 2-18 (Swift). This method uses NSLog or println to log all the books in the persistent store to the Xcode console.

Listing 2-17. The showExampleData Method in Objective-C

- (void)showExampleData {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Book"];

NSArray *books = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
for (Book *book in books) {
NSLog(@"Title: %@, price: %.2f", book.title, book.price);
}
}

Listing 2-18. The showExampleData Function in Swift

func showExampleData() {
let fetchRequest = NSFetchRequest(entityName: "Book")

let books = self.managedObjectContext?.executeFetchRequest(fetchRequest, error: nil)
for book in books as [Book] {
println(String(format: "Title: \(book.title), price: %.2f", book.price))
}
}

The method creates a new fetch request for the Book entity and executes it. Because the fetch request has no selection criteria, it simply returns all objects it finds in the persistent store that match the given entity (i.e., Book). If you run the application, you get the following output in the console:

BookStore[84975:c07] Title: The first book, price: 10.00
BookStore[84975:c07] Title: The third book, price: 10.00
BookStore[84975:c07] Title: The second book, price: 15.00

You may notice that the books may or may not be in order; in the preceding log, for example, the book with the title “The third book” comes before the book with the title “The second book.” Remember that the books relationship is not ordered, so the order isn’t deterministic.

Predicates

In the preceding fetch, we fetched all books from the Book entity, without any way to narrow the scope of what Core Data returned. Core Data does have mechanisms, however, to return only subsets of data. Core Data uses the NSPredicate class from the Foundation framework to specify how to select the objects that should be part of the result set when executing a fetch request. Predicates can be built in two ways: you can either build the object and its graph manually or you can use the query language NSPredicate implements. You will find that most of the time you will prefer the convenience and readability of the latter method.

In this section we look at both ways of building predicates for each example in order to help draw a parallel between the query language and the NSPredicate object graph. Understanding both ways will help you determine your preferred approach. You can also mix and match the approaches depending on your applications and data scenarios.

The predicate query language builds an NSPredicate object graph from a string representation that has similarities to an SQL ‘WHERE’ clause. The method that builds a predicate from the language is the class method predicateWithFormat:(NSString *). For instance, if you want to get the managed object that represents the book “The first book,” you build the predicate by calling [NSPredicate predicateWithFormat:@"title = 'The first book'"].

To test this, modify the showExampleData method as shown in Listing 2-19 (Objective-C) and Listing 2-20 (Swift).

Listing 2-19. The Objective-C showExampleData Method with a Predicate

- (void)showExampleData {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Book"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"title = 'The first book'"];

NSArray *books = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
for (Book *book in books) {
NSLog(@"Title: %@, price: %.2f", book.title, book.price);
}
}

Listing 2-20. The Swift showExampleData Function with a Predicate

func showExampleData() {
let fetchRequest = NSFetchRequest(entityName: "Book")
fetchRequest.predicate = NSPredicate(format: "title='The first book'")

let books = self.managedObjectContext?.executeFetchRequest(fetchRequest, error: nil)
for book in books as [Book] {
println(String(format: "Title: \(book.title), price: %.2f", book.price))
}
}

Run this and this time, the output finds only one book as expected.

BookStore[85253:c07] Title: The first book, price: 10.00

In its simplest form, a predicate executes a test on an object. If the test succeeds, then the object is part of the result set. If the test is negative, then the object isn’t included in the result set. The preceding example, title = 'The first book' is an example of a simple predicate. The key path title and the constant string value 'The first book' are called expressions. The operator '=' is called a comparator.

NSPredicate objects are made by compositing NSExpression instances. The example could have been written as shown in Listing 2-21 (Objective-C) and Listing 2-22 (Swift).

Listing 2-21. An Objective-C Predicate Using the NSPredicate Object Graph

- (void)showExampleData {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Book"];

NSExpression *exprTitle = [NSExpression expressionForKeyPath:@"title"];
NSExpression *exprValue = [NSExpression expressionForConstantValue:@"The first book"];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:exprTitle rightExpression:exprValue modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];
fetchRequest.predicate = predicate;

NSArray *books = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
for (Book *book in books) {
NSLog(@"Title: %@, price: %.2f", book.title, book.price);
}
}

Listing 2-22. A Swift Predicate Using the NSPredicate Object Graph

func showExampleData() {
let fetchRequest = NSFetchRequest(entityName: "Book")

let exprTitle = NSExpression(forKeyPath: "title")
let exprValue = NSExpression(forConstantValue: "The first book")
let predicate = NSComparisonPredicate(leftExpression: exprTitle, rightExpression: exprValue, modifier: .DirectPredicateModifier, type: .EqualToPredicateOperatorType, options: nil)
fetchRequest.predicate = predicate

let books = self.managedObjectContext?.executeFetchRequest(fetchRequest, error: nil)
for book in books as [Book] {
println(String(format: "Title: \(book.title), price: %.2f", book.price))
}
}

Running the application again will reveal the same result as before, as expected.

You’ve seen that predicates can be written in two ways. Obviously, the first one was simpler and more readable. In some advanced cases, however, the only option is the second. This could be the case, for example, in an application that lets users build queries and then composes the user’s selections and filters into predicates.

In Chapter 3 we study predicates more closely and explain the ins and outs of predicate building.

Fetched Properties

Fetched properties are a convenient way to retrieve subsets of data based on a predefined predicate. Imagine, for example, that we want to add a property to BookCategory to get the “bargain books” (i.e., all the books cheaper than $12). We can define what a bargain book is directly in the model. Open BookStore.xcdatamodeld and select the BookCategory entity. Click the + sign below the Fetched Properties section to create a new fetched property and name it bargainBooks. Open the Data Model inspector, and in the Destination drop-down, pick Book. For the predicate, enter

SELF.price < 12 AND (SELF.category=$FETCH_SOURCE)

Figure 2-6 shows the bargainBooks fetched property.

image

Figure 2-6. The bargainBooks Fetched Property

The SELF keyword relates to the destination (i.e., Book). The $FETCHED_SOURCE keyword refers to the BookCategory object. So the preceding code reads something like “this book belongs to the list if its price is less than $12 and its category is the calling category.”

Then in the code, rewrite the showExampleData method as shown in Listing 2-23 (Objective-C) or Listing 2-24 (Swift) to illustrate how to use the fetched property.

Listing 2-23. Using a Fetched Property in Objective-C

- (void)showExampleData {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"BookCategory"];
NSArray *categories = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
for (BookCategory *category in categories) {
// Retrieve the bargain books for the current category
NSArray *bargainBooks = [category valueForKey:@"bargainBooks"];

// Now simply display them
NSLog(@"Bargains for category: %@", category.name);
for (Book *book in bargainBooks) {
NSLog(@"Title: %@, price: %.2f", book.title, book.price);
}
}
}

Listing 2-24. Using a Fetched Property in Swift

func showExampleData() {
let fetchRequest = NSFetchRequest(entityName: "BookCategory")
let categories = self.managedObjectContext?.executeFetchRequest(fetchRequest, error: nil)
for category in categories as [BookCategory] {
println("Bargains for category: \(category.name)")
let bargainBooks = category.valueForKey("bargainBooks") as [Book!]
for book in bargainBooks {
println(String(format: "Title: \(book.title), price: %.2f", book.price))
}
}
}

We first fetch the categories. Then, for each category, we get the bargain books and display their titles.

Despite the convenience of defining fetched properties in our model, however, you’ll notice that the predicate editor is a bit anemic. Also, fetched properties aren’t dynamic; if you later add books, for example, that meet the criteria for bargain books, they aren’t present in thebargainBooks fetched properties until you reset the context. You may find that you never actually use fetched properties in your applications.

Notifications

When trying to keep a user interface synchronized with the data it represents, you will often find it useful to be notified when data change. Core Data comes with mechanisms for handling notifications on data changes.

Notification upon Creation

Sometimes you need to be notified each time an instance of a specific type of managed object is created. You may have requirements, for example, to create derived data that aren’t present in the store (cache files, perhaps, or simply for auditing reasons). Pretend we want to get a log entry every time a new book is created. We can use Core Data’s notifications, which call our custom Core Data class’s awakeFromInsert method each time a new object is created. In your implementation, you should call super’s implementation, and then perform your work. Open Book.mor Book.swift and override the awakeFromInsert method as shown in Listing 2-25 (Objective-C) and Listing 2-26 (Swift).

Listing 2-25. Core Data Notifications Through the awakeFromInsert Method (Objective-C)

- (void)awakeFromInsert {
[super awakeFromInsert];
NSLog(@"New book created");
}

Listing 2-26. Core Data Notifications Through the awakeFromInsert Function (Swift)

override func awakeFromInsert() {
super.awakeFromInsert()
println("New book created")
}

Since our application reloads the persistent store every time it launches, you will get three new book creation notifications each time you run it.

BookStore[85678:c07] New book created
BookStore[85678:c07] New book created
BookStore[85678:c07] New book created

Notification upon Fetch

Similarly, in some cases you want to be notified when an object is retrieved from the persistent store. For example, if your objects contain vectors to draw an image, upon fetching the object, you may want to render a thumbnail image. For storage reasons, you might decide that it’s redundant to persist the actual thumbnail and therefore you have to compute it when the object is fetched. To receive this notification, you override the awakeFromFetch method. As with awakeFromInsert, you should call the super implementation first. Still in Book.m or Book.swift, override the awakeFromFetch method as shown in Listing 2-27 (Objective-C) and Listing 2-28 (Swift).

Listing 2-27. Core Data Notifications Through the awakeFromFetch Method (Objective-C)

- (void)awakeFromFetch {
[super awakeFromFetch];
NSLog(@"Book fetched: %@", self.title);
}

Listing 2-28. Core Data Notifications Through the awakeFromFetch Method (Swift)

override func awakeFromFetch() {
super.awakeFromFetch()
println("Book fetched: \(self.title)")
}

When you run this, you get the following output:

BookStore[85678:c07] Book fetched: The first book
BookStore[85678:c07] Book fetched: The second book
BookStore[85678:c07] Book fetched: The third book
BookStore[85678:c07] New book created
BookStore[85678:c07] New book created
BookStore[85678:c07] New book created

You get the fetches first because of the cleanup in the deleteAllObjects method. First the books are read and deleted, then the three new books are added.

You might notice something a little strange, however, in the application’s output. Even though you fetch the book with the title “The first book” in the showExampleData method, as evidenced in the log

BookStore[85678:c07] Title: The first book, price: 10.00

You don’t see another log message saying “Book fetched,” even though showExampleData clearly executes a fetch request. This means that your awakeFromFetch isn’t necessarily called every time you execute a fetch request that returns it—it’s called only when it’s re-initialized from the persistent store during a fetch. If the object has already been initialized from the persistent store, and is sitting in your managed object context, executing a fetch request won’t call awakeFromFetch.

Though that may sound confusing, you can remember this behavior by focusing more on the “awake” part of the method name, rather than the “fetch” part. The object “awakes” when it is loaded into memory, so this method is called when a managed object is loaded into memory and that happened because of a fetch.

In Chapter 9, we talk about a concept called “faulting.” Faulting comes into play when the awakeFromFetch method is called, so be sure to read that chapter for more information.

Notification upon Change

By far the most useful notification is on data change. If you are displaying a book in your user interface and its title changes, you may want to the user interface to reflect this change. Since NSManagedObject is KVC compliant, all the usual principles of key-value observing (KVO) can be applied and notifications can be fired on data modifications. The KVO model is very simple and decentralized, with only two objects at play: the observer and the observed. The observer must be registered with the observed object and the observed object is responsible for firing notifications when its data change. The default implementation of NSManagedObject automatically handles the firing of notifications.

Registering an Observer

In order to receive change notifications, you must register the observer with the observed object using the addObserver:forKeyPath:options:context: method. The registration is valid for a given key path only, which means that it is valid only for the named attribute or relationship. The advantage is that it gives you a very fine level of control over which notifications fire.

The options give you an even finer level of granularity by specifying what information you want to receive in your notifications. Table 2-4 below shows the Objective-C options, which can be combined with a bitwise OR (for example, NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew to specify both old and new).

Table 2-4. The Objective-C Notification Options

Option

Description

NSKeyValueObservingOptionOld

The change dictionary will contain the old value under the lookup key NSKeyValueChangeOldKey

NSKeyValueObservingOptionNew

The change dictionary will contain the new value under the lookup key NSKeyValueChangeNewKey

The Swift versions of these options have briefer names, as Table 2-5 shows. The keys in the change directory, however, remain the same.

Table 2-5. The Swift Notification Options

Option

Description

Old

The change dictionary will contain the old value under the lookup key NSKeyValueChangeOldKey

New

The change dictionary will contain the new value under the lookup key NSKeyValueChangeNewKey

Let’s register the app delegate to be an observer to title changes for the book named “The first book.” Open AppDelegate.m or AppDelegate.swift and edit showExampleData, returning to the simpler version that fetches the first book, as shown in Listing 2-29 (Objective-C) andListing 2-30 (Swift). While we’re in the method, after registering for event notification, we also change the book title to “The new title.”

Listing 2-29. Registering for KVO in Objective-C

- (void)showExampleData {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Book"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"title = 'The first book'"];

NSArray *books = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
for (Book *book in books) {
NSLog(@"Title: %@, price: %.2f", book.title, book.price);

// Register this object for KVO
[book addObserver:self
forKeyPath:@"title"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:nil];
book.title = @"The new title";
}
}

Listing 2-30. Registering for KVO in Swift

func showExampleData() {
let fetchRequest = NSFetchRequest(entityName: "Book")
fetchRequest.predicate = NSPredicate(format: "title='The first book'")

let books = self.managedObjectContext?.executeFetchRequest(fetchRequest, error: nil)
for book in books as [Book] {
println(String(format: "Title: \(book.title), price: %.2f", book.price))

// Register this object for KVO
book.addObserver(self, forKeyPath: "title", options: .Old | .New, context: nil)
book.title = "The new title"
}
}

Don’t build and run the application yet, however, as it will crash because you’ve registered to receive a notification, but you haven’t yet written code to handle the notification once you’ve received it. Read on to the next section to understand how to receive notifications you’ve registered for.

Receiving the Notifications

Registering to receive the change notifications is one thing. Actually receiving the notifications is another. To receive change notifications, you override the observeValueForKeyPath:ofObject:change:context: method in the observer class. Still in AppDelegate.m or AppDelegate.swift, add the method shown in Listing 2-31 (Objective-C) and Listing 2-32 (Swift).

Listing 2-31. Receiving the Change Notification (Objective-C)

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
NSLog(@"Changed value for %@: %@ -> %@", keyPath,
[change objectForKey:NSKeyValueChangeOldKey],
[change objectForKey:NSKeyValueChangeNewKey]);
}

Listing 2-32. Receiving the Change Notification (Swift)

override func observeValueForKeyPath(keyPath: String,
ofObject object: AnyObject,
change: [NSObject : AnyObject],
context: UnsafeMutablePointer<()>) {
println("Changed value for \(keyPath): \(change[NSKeyValueChangeOldKey]!) -> \(change[NSKeyValueChangeNewKey]!)")
}

Run the application once again and you will get the following output, indicating that the title change notification has been received:

Changed value for title: The first book -> The new title

Conclusion

For applications that depend on data, which means most applications, the importance of designing your data model correctly cannot be overstated. The Xcode data modeling tool provides a solid interface for designing and defining your data models, and throughout this chapter you have learned to navigate the many options available as you create entities, attributes, and relationships. Make sure to understand the implications of how you've configured your data model, so that your applications can run efficiently and correctly.

In most cases, you should avoid options in your model that wrest control from Core Data and make you manage the consistency of the object graph yourself. Core Data will almost always do a better job of managing object graph consistency than you will. When you must take control, make sure to debug your solutions well to prevent application crashes or data corruption.

While you can work with NSManagedObject directly, you will find that as you build larger applications, you will inevitably feel the need to use subclasses, as we discussed in this chapter, in order to keep your objects and their properties clearly delineated in code.

In the next chapter, we take an in-depth look at building predicates for advanced queries.