Chapter    12

Core Data: What, Why, and How

Core Data is a framework and set of tools that allow you to save (or persist) your application’s data to an iOS device’s file system automatically. Core Data is an implementation of something called object-relational mapping (ORM). This is just a fancy way of saying that Core Data allows you to interact with your Objective-C objects without having to worry about how the data from those objects is stored and retrieved from persistent data stores such as relational database (such as SQLite) or into a flat file.

Core Data can seem like magic when you first start using it. Core Data objects are, for the most part, handled just like plain old objects, and they seem to know how to retrieve and save themselves automagically. You won’t create SQL strings or make file management calls, ever. Core Data insulates you from some complex and difficult programming tasks, which is great for you. By using Core Data, you can develop applications with complex data models much, much faster than you could using straight SQLite, object archiving, or flat files.

Technologies that hide complexity the way Core Data does can encourage “voodoo programming,” that most dangerous of programming practices where you include code in your application that you don’t necessarily understand. Sometimes that mystery code arrives in the form of a project template. Or, perhaps you download a utilities library that does a task for you that you just don’t have the time or expertise to do for yourself. That voodoo code does what you need it to do, and you don’t have the time or inclination to step through it and figure it out, so it just sits there, working its magic… until it breaks. As a general rule, if you find yourself with code in your own application that you don’t fully understand, it’s a sign you should go do a little research, or at least find a more experienced peer to help you get a handle on your mystery code.

The point is that Core Data is one of those complex technologies that can easily turn into a source of mystery code that will make its way into many of your projects. Although you don’t need to know exactly how Core Data accomplishes everything it does, you should invest some time and effort into understanding the overall Core Data architecture.

This chapter starts with a brief history of Core Data and then it dives into a Core Data application. By building a Core Data application with Xcode, you’ll find it much easier to understand the more complex Core Data projects you’ll find in the following chapters.

A Brief History of Core Data

Core Data has been around for quite some time, but it became available on iOS with the release of iPhone SDK 3.0. Core Data was originally introduced with Mac OS X 10.4 (Tiger), but some of the DNA in Core Data actually goes back about 15 years to a NeXT framework called Enterprise Objects Framework (EOF), which was part of the toolset that shipped with NeXT’s WebObjects web application server.

EOF was designed to work with remote data sources, and it was a pretty revolutionary tool when it first came out. Although there are now many good ORM tools for almost every language, when WebObjects was in its infancy, most web applications were written to use handcrafted SQL or file system calls to persist their data. Back then, writing web applications was incredibly time- and labor-intensive. WebObjects, in part because of EOF, cut the development time needed to create complex web applications by an order of magnitude.

In addition to being part of WebObjects, EOF was also used by NeXTSTEP, which was the predecessor to Cocoa. When Apple bought NeXT, the Apple developers used many of the concepts from EOF to develop Core Data. Core Data does for desktop applications what EOF had previously done for web applications: it dramatically increases developer productivity by removing the need to write file system code or interact with an embedded database.

Let’s start building your Core Data application.

Creating a Core Data Application

Fire up Xcode and create a new Xcode project. There are many ways to do this. When you start Xcode, you may get the Xcode startup window (Figure 2-1). You can just click the area titled “Create a New Xcode Project.” Or you can select File image New image Project. Or you can use the keyboard shortcut imageN. Whatever floats your boat. Going forward, we’re going to mention the options available in the Xcode window or the menu options, but we won’t use the keyboard shortcut. If you know and prefer the keyboard shortcuts, feel free to use them. Let’s get back to building your app.

9781430238072_Fig02-01.jpg

Figure 2-1.  Xcode startup window

Xcode will open a project workspace and display the Project Template sheet (Figure 2-2). On the left are the possible template headings: iOS and OS X. Each heading has a bunch of template groups. Select the Application template group under the iOS heading, and then select Master-Detail Application template on the right. On the bottom right, there’s a short description of the template. Click the Next button to move the next sheet.

9781430238072_Fig02-02.jpg

Figure 2-2.  Project Template sheet

The next sheet is the Project Configuration sheet (Figure 2-3). You’ll be asked to provide a product name; use the name CoreDataApp. The Organization Name and Company Identifier fields will be set automatically by Xcode; by default these will read MyCompanyName and com.mycompanyname. You can change these to whatever you like, but for the Company Identifier, Apple recommends using the reverse domain name style (such as com.apporchard).

9781430238072_Fig02-03.jpg

Figure 2-3.  Project Configuration sheet

Note that the Bundle Identifier field is not editable; rather it’s populated by the values from the Company Identifier and Product Name fields. The Class Prefix field is an option to add a prefix (i.e., NS) before all your project’s classes. You can leave this blank.

The Devices drop-down field lists the possible target devices for this project: iPad, iPhone, or Universal. The first two are self-explanatory. “Universal” is for applications that will run on both the iPad and iPhone. It’s a blessing and a curse to have to a single project that can support both iPads and iPhones. But for the purposes of this book, you’ll stick with iPhone. Since you’ll be using storyboards, make sure the “Use Storyboards” checkbox is checked. You obviously want to use Core Data, so check its checkbox. Finally, make sure the “Use Automatic Reference Counting” checkbox is checked.

Click Next, and choose a location to save your project (Figure 2-4). The checkbox on the bottom will set up your project to use Git (www.git-scm.com), a free, open-source version control system. It’s useful to use so you can leave it checked. We won’t discuss it, but if you don’t know about version control or git, we suggest you get familiar with them. Click Create. Xcode should create your project, and it should look like Figure 2-5.

9781430238072_Fig02-04.jpg

Figure 2-4.  Choose a location to put your project

9781430238072_Fig02-05.jpg

Figure 2-5.  Voila, your project is ready!

Build and run the application. Either press the Run button on the Toolbar, or Product image Run. The simulator should appear. Press the Add (+) button in the upper right. A new row will insert into the table that shows the exact date and time the Add button was pressed (Figure 2-6). You can also use the Edit button to delete rows. Exciting, huh?

9781430238072_Fig02-06.jpg

Figure 2-6.  CoreDataApp in action

Under the hood of this simple application, a lot is happening. Think about it: without adding a single class, or any code to persist data to a file or interact with a database, pressing the Add button created an object, populated it with data, and saved it to a SQLite database created for you automatically. There’s plenty of free functionality here.

Now that you’ve seen an application in action, let’s take a look at what’s going on behind the scenes.

Core Data Concepts and Terminology

Like most complex technologies, Core Data has its own terminology that can be a bit confusing to newcomers. Let’s break down the mystery and get your arms around Core Data’s nomenclature.

Figure 2-7 shows a simplified, high-level diagram of the Core Data architecture. Don’t expect it all to make sense now, but as you look at different pieces, you might want to refer back to this diagram to cement your understanding of how they fit together.

There are five key concepts to focus on here. As you proceed through this chapter, make sure you understand each of the following:

  • Data Model
  • Persistent Store
  • Persistent Store Coordinator
  • Managed Object and Managed Object Context
  • Fetch Request

Once again, don’t let the names throw you. Follow along and you’ll see how all these pieces fit together.

9781430238072_Fig02-07.jpg

Figure 2-7.  The Core Data architecture

The Data Model

What is a data model? In an abstract sense, it’s an attempt to define the organization of data and the relationship between the organized data components. In Core Data, the data model defines the data structure of objects, the organization of those objects, the relationships between those objects, and the behavior of those objects. Xcode allows you, via the model editor and inspector, to specify your data model for use in your application.

If you expand the CoreDataApp group in the Navigator content pane, you’ll see a file called CoreDataApp.xcdatamodel. This file is the default data model for your project. Xcode created this file for you because you checked the “Use Core Data” checkbox in the Project Configuration sheet. Single-click CoreDataApp.xcdatamodel to bring up Xcode’s model editor. Make sure the Utility pane is visible (it should be the third button on the View bar), and select the Inspector. Your Xcode window should look like Figure 2-8.

9781430238072_Fig02-08.jpg

Figure 2-8.  Xcode with the model editor and inspector

When you selected the data model file, CoreDataApp.xcdatamodel, the Editor pane changed to present the Core Data model editor (Figure 2-9). Along the top, the jump bar remains unchanged. Along the left, the gutter has been replaced by a wider pane, the Top-Level Components pane. The Top-Level Components pane outlines the entities, fetch requests, and configurations defined in the data model (we’ll cover these in detail in a little bit). You can add a new entity by using the Add Entity button at the bottom of the Top-Level Components pane. Alternately, you can use the Editor image Add Entity menu option. If you click and hold the Add Entity button, you will be presented with a pop-up menu of choices: Add Entity, Add Fetch Request, and Add Configuration. Whatever option you choose, the single-click behavior of the button will change to that component and the label of the button will change to reflect this behavior. The menu equivalents for adding fetch requests and configurations can be found in the Editor menu below the Add Entity menu item.

9781430238072_Fig02-09.jpg

Figure 2-9.  A close look at the model editor

The Top-Level Components pane has two styles: list and hierarchical. You can toggle between these two styles by using the Outline Style selector group found at the bottom of the Top-Level Components pane. Switching styles with the CoreDataApp data model won’t change anything in the Top-Level Components pane, as there’s only one entity and one configuration, so there’s no hierarchy to be shown. If you had a component that depended on another component, you’d see the hierarchical relationship between the two with the hierarchical outline style.

The bulk of the Editor pane is taken up by the Detail editor. The Detail editor has two editor styles: table and graph. By default (and pictured in Figure 2-9), the Detail editor is in table style. You can toggle between these styles by using the Editor Style selector group on the bottom right of the Editor pane. Try it. You can see the difference in the two styles.

When you select an entity in the Top-Level Components pane, the Detail editor will display, in table style, three tables: Attributes, Relationships, and Fetched Properties. Again, we’ll cover these in detail in a little bit. You can add a new attribute by using the Add Attribute button below the Detail editor. Similar to the Add Entity button, a click-and-hold will reveal a pop-up menu of choices: Add Attribute, Add Relationship, and Add Fetched Property. Again, the single-click behavior of this button will change depending on your choice, with its label reflecting that behavior. Under the Editor menu there are three menu items: Add Attribute, Add Relationship, and Add Fetched Property. These are only active when an entity is selected in the Top-Level Components pane.

If you switch the Detail editor to graph style, you’ll see a large grid with a single rounded square in the center. This rounded square represents the entity in the Top-Level Components pane. The template you used for this project creates a single entity, Event. Selecting Event in the Top Level Components pane is the same as selecting the rounded rectangle in the graph view.

Try it. Click outside the entity in the Detail editor grid to deselect it, and then click the Event line in the Top-Level Components pane. The entity in the graph view will also be selected. The Top Level Components pane and the graph view show two different views of the same entity list.

When unselected, the title bar and lines of the Event entity square should be pink. If you select the Event entity in the Top-Level Components pane, the Event entity in the Detail editor should change color to a blue, indicating it’s selected. Now click anywhere on the Detail editor grid, outside the Event rounded square. The Event entity should be deselected in the Top Level Components pane and should change color in the Detail editor. If you click on the Event entity in the Detail editor, it will be selected again. When selected, the Event entity should have a resize handle (or dot) on the left and right sides, allowing you to resize its width.

You are currently given the Event entity. It has a single attribute, named timeStamp, and no relationships. The Event entity was created as part of this template. As you design your own data models, you’ll most likely delete the Event entity and create your own entities from scratch. A moment ago, you ran your Core Data sample application in the simulator. When you pressed the + icon, a new instance of Event was created. Entities, which we’ll look at more closely in a few pages, replace the Objective-C data model class you would otherwise use to hold your data. We’ll get back to the model editor in just a minute to see how it works. For now, just remember that the persistent store is where Core Data stores its data, and the data model defines the form of that data. Also remember that every persistent store has one, and only one, data model.

The inspector provides greater detail for the item(s) selected in the model editor. Since each item could have a different view in the inspector, we’ll discuss the details as we discuss the components and their properties. That being said, let’s discuss the three top-level components: entities, fetch requests, and configurations.

Entities

An entity can be thought as the Core Data analogue to an Objective-C class declaration. In fact, when using an entity in your application, you essentially treat it like an Objective-C class with some Core Data-specific implementation. You use the model editor to define the properties that define your entity. Each entity is given a name (in this case, Event), which must begin with a capital letter. When you ran CoreDataApp earlier, every time you pressed the Add (+) button, a new instance of Event was instantiated and stored in the application’s persistent store.

Make sure the Utility pane is exposed, and select the Event entity. Now look at the inspector in the Utility pane (make sure the enspector is showing by selecting the Inspector button in the Inspector selector bar). Note that the Inspector pane now allows you to edit or change aspects of the entity (Figure 2-10). We’ll get to the details of the inspector later.

Properties

While the Editor pane lists all the data model’s entities, the Inspector pane allows you to “inspect” the properties that belong to the selected entity. An entity can be made up of any number of properties. There are three different types of properties: attributes, relationships, and fetched properties. When you select an entity’s property in the model editor, the property’s details are displayed in the Inspector pane.

Attributes

The property that you’ll use the most when creating entities is the attribute, which serves the same function in a Core Data entity as an instance variable does in an Objective-C class: they both hold data. If you look at your model editor (or at Figure 2-10), you’ll see that the Event entity has one attribute named timeStamp. The timeStamp attribute holds the date and time when a given Event instance was created. In your sample application, when you click the + button, a new row is added to the table displaying a single Event’s timeStamp.

Just like an instance variable, each attribute has a type. There are two ways to set an attribute’s type. When the model editor is using the table style, you can change an attributes type in the Attributes table in the Detail editor (Figure 2-11). In your current application, the timeStamp attribute is set to the date type. If you click on the Date cell, you’ll see a pop-up menu. That pop-up menu shows the possible attribute types. You’ll look at the different attribute types in the next few chapters when you begin building your own data models.

9781430238072_Fig02-10.jpg

Figure 2-10.  The inspector for the Event entity

9781430238072_Fig02-11.jpg

Figure 2-11.  Attributes table in the model editor, table style

Make sure that the timeStamp attribute is still selected, and take a look at the inspector (Figure 2-12). Notice among the fields there is an Attribute Type field with a pop-up button. Click the button, and a pop-up menu should appear. It should contain the attribute’s types you saw in the Attribute table. Make sure the attribute type is set to date.

9781430238072_Fig02-12.jpg

Figure 2-12.  Inspector for the timeStamp attribute

A date attribute, such as timeStamp, corresponds to an instance of NSDate. If you want to set a new value for a date attribute, you need to provide an instance of NSDate to do so. A string attribute corresponds to an instance of NSString, and most of the numeric types correspond to an instance of NSNumber.

Tip  Don’t worry too much about all the other buttons, text fields, and checkboxes in the model editor. As you make your way through the next few chapters, you’ll get a sense of what each does.

Relationships

As the name implies, a relationship defines the associations between two different entities. In the template application, no relationships are defined for the Event entity. We’ll begin discussing relationships in Chapter 7, but here’s an example just to give you a sense of how they work.

Suppose you created an Employee entity and wanted to reflect each Employee’s employer in the data structure. You could just include an employer attribute, perhaps an NSString, in the Employee entity, but that would be pretty limiting. A more flexible approach would be to create an Employer entity, and then create a relationship between the Employee and Employer entities.

Relationships can be to one or to many, and they are designed to link specific objects. The relationship from Employee to Employer might be a to-one relationship, if you assume that your Employees do not moonlight and have only a single job. On the other hand, the relationship from Employer to Employee is to many, since an Employer might employ many Employees.

To put this in Objective-C terms, a to-one relationship is like using an instance variable to hold a pointer to an instance of another Objective-C class. A to-many relationship is more like using a pointer to a collection class like NSMutableArray or NSSet, which can contain multiple objects.

Fetched Properties

A fetched property is like a query that originates with a single managed object. For example, suppose you add a birthdate attribute to Employee. You might add a fetched property called sameBirthdate to find all Employees with the same birthdate as the current Employee.

Unlike relationships, fetched properties are not loaded along with the object. For example, if Employee has a relationship to Employer, when an Employee instance is loaded, the corresponding Employer instance will be loaded, too. But when an Employee is loaded, sameBirthdate is not evaluated. This is a form of lazy loading. You’ll learn more about fetched properties in Chapter 7.

Fetch Requests

While a fetched property is like a query that originates with a single managed object, a fetch request is more like a class method that implements a canned query. For example, you might build a fetch request named canChangeLightBulb that returns a list of Employees who are taller than 80 inches (about 2 meters). You can run the fetch request any time you need a light bulb changed. When you run it, Core Data searches the persistent store to find the current list of potential light-bulb-changing Employees.

You will create many fetch requests programmatically in the next few chapters, and you’ll be looking at a simple one a little later in this chapter in the “Creating a Fetched Results Controller” section.

Configurations

A configuration is a set of entities. Different configurations may contain the same entity. Configurations are use to define which entities are stored in which persistent store. Most of the time, you won’t need anything other than the default configuration. We won’t cover using multiple configurations in this book. If you want to learn more, check the Apple Developer site or Pro Core Data for iOS, 2nd Edition (www.apress.com/9781430236566).

The Data Model Class: NSManagedObjectModel

Although you won’t typically access your application’s data model directly, you should be aware of the fact that there is an Objective-C class that represents the data model in memory. This class is called NSManagedObjectModel, and the template automatically creates an instance of NSManagedObjectModel based on the data model file in your project. Let’s take a look at the code that creates it now.

In the Navigation pane, open the CoreDataApp group and AppDelegate.m. In the Editor jump bar, click the last menu (it should read No Selection) to bring up a list of the methods in this class (see Figure 2-13). Select -managedObjectModel in the Core Data Stack section, which will take you to the method that creates the object model based on the CoreDataApp.xcdatamodel file.

9781430238072_Fig02-13.jpg

Figure 2-13.  The editor pane set to show counterparts will allow you to see the declaration and implementation

The method should look like this:

// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataApp"
                                              withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

The first checks the instance variable _managedObjectModel to see if it’s nil. This accessor method uses a form of lazy loading. The underlying instance variable doesn’t actually get instantiated until the first time the accessor method is called. For this reason, you should never, ever access _managedObjectModel directly (except within the accessor method itself, of course). Always make sure to use the accessor methods. Otherwise, you could end up trying to make calls on an object that hasn’t been created yet.

Tip  The data model class is called NSManagedObjectModel because, as you’ll see a little later in the chapter, instances of data in Core Data are called managed objects.

If _managedObjectModel is nil, you can go get your data model. By default XCode should have written the following two lines of code to accomplish this for you:

NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataApp"
                                          withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

Remember how we said that a persistent store was associated with a single data model? Well, that’s true, but it doesn’t tell the whole story. You can combine multiple .xcdatamodel files into a single instance of NSManagedObjectModel, creating a single data model that combines all the entities from multiple files. If you are planning on having more than one model, you can change those two lines of code to one.

This one line of code will take any .xcdatamodel files that might be in your Xcode project and combines them together into a single instance of NSManagedObjectModel:

_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];

So, for example, if you create a second data model file and add it to your project, that new file will be combined with CoreDataApp.xcdatamodel into a single managed object model that containes the contents of both files. This allows you to split up your application’s data model into multiple smaller and more manageable files.

The vast majority of iOS applications that use Core Data have a single persistent store and a single data model, so the default template code will work beautifully most of the time. That said, Core Data does support the use of multiple persistent stores. You could, for example, design your application to store some of its data in a SQLite persistent store and some of it in a binary flat file. If you find that you need to use multiple data models, remember to change the template code here to load the managed object models individually using mergedModelFromBundles:.

The Persistent Store and Persistent Store Coordinator

The persistent store, which is sometimes referred to as a backing store, is where Core Data stores its data. By default, on iOS devices Core Data uses a SQLite database contained in your application’s Documents folder as its persistent store. But this can be changed without impacting any of the other code you write by tweaking a single line of code. We’ll show you the actual line of code to change in a few moments.

Caution  Do not change the type of persistent store once you have posted your application to the App Store. If you must change it for any reason, you will need to write code to migrate data from the old persistent store to the new one, or else your users will lose all of their data—something that will almost always make them quite unhappy.

Every persistent store is associated with a single data model, which defines the types of data that the persistent store can store.

The persistent store isn’t actually represented by an Objective-C class. Instead, a class called NSPersistentStoreCoordinator controls access to the persistent store. In essence, it takes all the calls coming from different classes that trigger reads or writes to the persistent store and serializes them so that multiple calls against the same file are not being made at the same time, which could result in problems due to file or database locking.

As is the case with the managed object model, the template provides you with a method in the application delegate that creates and returns an instance of a persistent store coordinator. Other than creating the store and associating it with a data model and a location on disk (which is done for you in the template), you will rarely need to interact with the persistent store coordinator directly. You’ll use high-level Core Data calls, and Core Data will interact with the persistent store coordinator to retrieve or save the data.

Let’s take a look at the method that returns the persistent store coordinator. In CoreDataAppDelegate.m, select -persistentStoreCoordinator from the function pop-up menu. Here’s the method:

// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    NSURL *storeURL = [[self applicationDocumentsDirectory]
          URLByAppendingPathComponent:@"CoreDataApp.sqlite"];
    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
                                          initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                              configuration:nil
                                        URL:storeURL
                                    options:nil
                                      error:&error]) {
        /*
         Replace this implementation with code to handle the error appropriately.
         abort() causes the application to generate a crash log and terminate. You should not use
         this function in a shipping application, although it may be useful during development.
         Typical reasons for an error here include:
         * The persistent store is not accessible;
         * The schema for the persistent store is incompatible with current managed object model.
         Check the error message to determine what the actual problem was.
         If the persistent store is not accessible, there is typically something wrong with the file
         path. Often, a file URL is pointing into the application's resources directory instead of a
         writeable directory.
         If you encounter schema incompatibility errors during development, you can reduce their
             frequency by:
         * Simply deleting the existing store:
         [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
         * Performing automatic lightweight migration by passing the following dictionary as the
             options parameter:
         @{NSMigratePersistentStoresAutomaticallyOption:@YES,
                    NSInferMappingModelAutomaticallyOption:@YES}
         Lightweight migration will only work for a limited set of schema changes; consult "Core Data
         Model Versioning and Data Migration Programming Guide" for details.
         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    return _persistentStoreCoordinator;
}

As with the managed object model, this persistentStoreCoordinator accessor method uses lazy loading and doesn’t instantiate the persistent store coordinator until the first time it is accessed. Then it creates a path to a file called CoreDataApp.sqlite in the Documents directory in your application’s sandbox. The template will always create a filename based on your project’s name. If you want to use a different name, you can change it here, though it generally doesn’t matter what you call the file since the user will never see it.

Caution  If you do decide to change the filename, make sure you don’t change it after you’ve posted your application to the App Store, or else future updates will cause your users to lose all of their data.

Take a look at this line of code:

if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                 configuration:nil
                                 URL:storeURL
                                 options:nil
                                 error:&error]) {

The first parameter to this method, NSSQLiteStoreType, determines the type of the persistent store. NSSQLiteStoreType is a constant that tells Core Data to use a SQLite database for its persistent store. If you want your application to use a single, binary flat file instead of a SQLite database, you could specify the constant NSBinaryStoreType instead of NSSQLiteStoreType. The vast majority of the time, the default setting is the best choice, so unless you have a compelling reason to change it, leave it alone.

Note  A third type of persistent store supported by Core Data on iOS devices is called in-memory store. The primary use of this option is to create a caching mechanism, storing the data in memory instead of in a database or binary file. To use an in-memory store, specify a store type of NSInMemoryStoreType.

Reviewing the Data Model

Before you move on to other parts of Core Data, let’s quickly review how the pieces you’ve looked at so far fit together. You might want to refer back to Figure 2-7.

The persistent store (or backing store) is a file on an iOS device’s file system that can be either a SQLite database or a binary flat file. A data model file, contained in one or more files with an extension of .xcdatamodel, describes the structure of your application’s data. This file can be edited in Xcode. The data model tells the persistent store coordinator the format of all data stored in that persistent store. The persistent store coordinator is used by other Core Data classes that need to save, retrieve, or search for data. Easy enough, right? Let’s move on.

Managed Objects

Entities define the structure of your data, but they do not actually hold any data themselves. The instances of data are called managed objects. Every instance of an entity that you work with in Core Data will be an instance of the class NSManagedObject or a subclass of NSManagedObject.

Key-Value Coding

The NSDictionary class allows you to store objects in a data structure and retrieve an object using a unique key. Like the NSDictionary class, NSManagedObject supports the key-value methods valueForKey: and setValue:forKey: for setting and retrieving attribute values. It also has additional methods for working with relationships. You can, for example, retrieve an instance of NSMutableSet representing a specific relationship. Adding managed objects to this mutable set or removing them will add or remove objects from the relationship it represents.

If the NSDictionary class is new to you, take a few minutes to fire up Xcode and read about NSDictionary in the documentation viewer. The important concept to get your head around is key-value coding, or KVC. Core Data uses KVC to store and retrieve data from its managed objects.

In your template application, consider an instance of NSManagedObject that represents a single event. You could retrieve the value stored in its timeStamp attribute by calling valueForKey:, like so:

NSDate *timeStamp = [managedObject valueForKey:@"timeStamp"];

Since timeStamp is an attribute of type date, you know the object returned by valueForKey: will be an instance of NSDate. Similarly, you could set the value using setValue:ForKey:. The following code would set the timeStamp attribute of managedObject to the current date and time:

[managedObject setValue:[NSDate date] forKey:@"timeStamp"];

KVC also includes the concept of a keypath. Keypaths allow you iterate through object hierarchies using a single string. So, for example, if you had a relationship on your Employee entity called whereIWork, which pointed to an entity named Employer, and the Employer entity had an attribute called name, then you could get to the value stored in name from an instance of Employee using a keypath like so:

NSString *employerName = [managedObject valueForKeyPath:@"whereIWork.name"];

Notice that you use valueForKeyPath: instead of valueForKey:, and you provide a dot-separated value for the keypath. KVC parses that string using the dots, so in this case, it would parse it into two separate values: whereIWork and name. It uses the first one (whereIWork) on itself and retrieves the object that corresponds to that key. It then takes the next value in the keypath (name) and retrieves the object stored under that key from the object returned by the previous call. Since Employer is a to-one relationship, the first part of the keypath would return a managed object instance that represented the Employee’s employer. The second part of the keypath would then be used to retrieve the name from the managed object that represents the Employer.

Note  If you’ve used bindings in Cocoa, you’re probably already familiar with KVC and keypaths. If not, don’t worry—they will become second nature to you before long. Keypaths are really quite intuitive.

Managed Object Context

Core Data maintains an object that acts as a gateway between your entities and the rest of Core Data. That gateway is called a managed object context (often just referred to as a context). The context maintains state for all the managed objects that you’ve loaded or created. The context keeps track of changes that have been made since the last time a managed object was saved or loaded. When you want to load or search for objects, for example, you do it against a context. When you want to commit your changes to the persistent store, you save the context. If you want to undo changes to a managed object, you just ask the managed object context to undo. (Yes, it even handles all the work needed to implement undo and redo for your data model).

When building iOS applications, you will have only a single context the vast majority of the time. However, iOS makes having more than one context easy. You can create nested managed object contexts, in which the parent object store of a context is another managed object context rather than the persistent store coordinator.

In this case, fetch and save operations are mediated by the parent context instead of by a coordinator. You can imagine a number of usage scenarios, including things like performing background operations on a second thread or queue and managing discardable edits from an inspector window or view. A word of caution: nested contexts make it more important than ever that you adopt the “pass the baton” approach of accessing a context (by passing a context from one view controller to the next) rather than retrieving it directly from the application delegate.

Because every application needs at least one managed object context to function, the template has very kindly provided you with one. Click AppDelegate.m again, and select -managedObjectContext from the Function menu in the Editor Jump Bar. You will see a method that looks like this:

// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

This method is actually pretty straightforward. Using lazy loading, _managedObjectContext is checked for nil. If it is not nil, its value is returned. If managedObjectContext is nil, you check to see if your NSPersistentStoreCoordinator exists. If so, you create a new _managedObjectContext, then use setPersistentStoreCoordinator: to tie the current coordinator to your managedObjectContext. When you’re finished, you return _managedObjectContext.

Note  Managed object contexts do not work directly against a persistent store; they go through a persistent store coordinator. As a result, every managed object context needs to be provided with a pointer to a persistent store coordinator in order to function. Multiple managed object contexts can work against the same persistent store coordinator, however.

Saves On Terminate

While you’re in the application delegate, scroll up to another method called applicationWillTerminate:, which saves changes to the context if any have been made. The changes are saved to the persistent store. As its name implies, this method is called just before the application exits.

- (void)applicationWillTerminate:(UIApplication *)application
{
    // Saves changes in the application's managed object context before the application
    // terminates.
    [self saveContext];
}

This is a nice bit of functionality, but there may be times when you don’t want the data to be saved. For example, what if the user quits after creating a new entity, but before entering any data for that entity? In that case, do you really want to save that empty managed object into the persistent store? Possibly not. You’ll look at dealing with situations like that in the next few chapters.

Load Data From the Persistent Store

Run the Core Data application you built earlier and press the plus button a few times (see Figure 2-6). Quit the simulator, and then run the application again. Note that the timestamps from your previous runs were saved into the persistent store and loaded back in for this run.

Click MasterViewController.m so you can see how this happens. As you can probably guess from the filename, MasterViewController is the view controller class that acts as your application’s, well, master view controller. This is the view controller for the view you can see in Figure 2-6.

Once you’ve clicked the filename, you can use the Editor jump bar’s Function menu to find the viewDidLoad: method, although it will probably be on your screen already since it’s the first method in the class. The default implementation of the method looks like this:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.navigationItem.leftBarButtonItem = self.editButtonItem;
    UIBarButtonItem *addButton =     [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd                                                     target:self                                                     action:@selector(insertNewObject:)];
    self.navigationItem.rightBarButtonItem = addButton;
}

The first thing the method does is call super. Next, it sets up the Edit and Add buttons. Note that MasterViewController inherits from UITableViewController, which in turn inherits from UIViewController. UIViewController provides a property named editButtonItem, which returns an Edit button. Using dot notation, you retrieve editButtonItem and pass it to the leftBarButtonItem property of the navigationItem property. Now the Edit button is the left button in the navigation bar.

Now let’s focus on the Add button. Since UIViewController does not provide an Add button, use alloc to create one from scratch and the add it as the right button in the navigation bar. The code is fairly straightforward:

UIBarButtonItem *addButton =     [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
                                                  target:self
                                                  action:@selector(insertNewObject:)];
self.navigationItem.rightBarButtonItem = addButton;

So, with the basic user interface set up, it’s time to look at how the fetched results controller works.

The Fetched Results Controller

Conceptually speaking, the fetched results controller isn’t quite like the other generic controllers you’ve seen in the iOS SDK. If you’ve used Cocoa bindings and the generic controller classes available on the Mac, such as NSArrayController, then you’re already familiar with the basic idea. If you’re not familiar with those generic controller classes, a little explanation is probably in order.

Most of the generic controller classes in the iOS SDK (such as UINavigationController, UITableViewController, and UIViewController) are designed to act as the controller for a specific type of view. View controllers, however, are not the only types of controller classes that Cocoa Touch provides, although they are the most common. NSFetchedResultsController is an example of a controller class that is not a view controller.

NSFetchedResultsController is designed to handle one very specific job, which is to manage the objects returned from a Core Data fetch request. NSFetchedResultsController makes displaying data from Core Data easier than it would otherwise be because it handles a bunch of tasks for you. It will, for example, purge any unneeded objects from memory when it receives a low-memory warning and reload them when it needs them again. If you specify a delegate for the fetched results controller, your delegate will be notified when certain changes are made to its underlying data.

Creating a Fetched Results Controller

You start by creating a fetch request and then use that fetch request to create a fetched results controller. In your template, this is done in MasterViewController.m, in the fetchedResultsController method. fetchedResultsController starts with a lazy load to see if there is already an active instantiated _fetchedResultsController. If that is not there (resolves to nil), it sets out to create a new fetch request. A fetch request is basically a specification that lays out the details of the data to be fetched. You need to tell the fetch request which entity to fetch. In addition, you want to add a sort descriptor to the fetch request. The sort descriptor determines the order in which the data is organized.

Once the fetch request is defined appropriately, the fetched results controller is created. The fetched results controller is an instance of the class NSFetchedResultsController. Remember that the fetched results controller’s job is to use the fetch request to keep its associated data as fresh as possible.

Once the fetched results controller is created, you do your initial fetch. You do this in MasterViewController.m at the end of fetchedResultsController by sending your fetched results controller the PerformFetch message.

Now that you have your data, you’re ready to be a data source and a delegate to your table view. When your table view wants the number of sections for its table, it will call numberOfSectionsInTableView:. In your version, you get the section information by passing the appropriate message to fetchResultsController. Here’s the version from MasterViewController.m:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.fetchedResultsController sections] count];
}

The same strategy applies in tableView:numberOfRowsInSection:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id <NSFetchedResultsSectionInfo> sectionInfo =         [self.fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

You get the idea. You used to need to do all this work yourself. Now you can ask your fetched results controller to do all the data management for you. It’s an amazing time-saver!

Let’s take a closer look at the creation of the fetched results controller. In MasterViewController.m, use the function menu to go to the method -fetchedResultsController. It should look like this:

- (NSFetchedResultsController *)fetchedResultsController
{
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event"
                        inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];
    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp"
                                                                ascending:NO];
    NSArray *sortDescriptors = @[sortDescriptor];
    [fetchRequest setSortDescriptors:sortDescriptors];
    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
                   initWithFetchRequest:fetchRequest
                   managedObjectContext:self.managedObjectContext
                   sectionNameKeyPath:nil cacheName:@"Master"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;
 NSError *error = nil;
 if (![self.fetchedResultsController performFetch:&error]) {
      // Replace this implementation with code to handle the error appropriately.
      // abort() causes the application to generate a crash log and terminate. You should
             // not use this function in a shipping application, although it may be useful during
             // development.
     NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
     abort();
 }
    return _fetchedResultsController;
}

As discussed earlier, this method uses lazy loading. The first thing it does is check _fetchedResultsController for nil. If _fetchedResultsController already exists, it is returned; otherwise, the process of creating a new fetchedResultsController is started.

As the first step, you need to create an NSFetchRequest and NSEntityDescription, and then attach the NSEntityDescription to the NSFetchRequest.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event"                                          inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];

Remember, you’re building a fetched results controller, and the fetch request is part of that. Next, set the batch size to 20. This tells Core Data that this fetch request should retrieve its results 20 at a time. This is sort of like a file system’s block size.

// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];

Next, build an NSSortDescriptor and specify that it use timeStamp as a key, sorting the timestamps in descending order (earlier dates last).

// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO];

Now you create an array of sort descriptors. Since you’ll be using only one, you pass in sortDescriptor and follow it with nil to let initWithObjects know you just have a single element in the array. (Note that the template could have used initWithObject instead).

NSArray *sortDescriptors = @[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];

Try this experiment: change ascending:NO to ascending:YES and run the application again. What do you think will happen? Don’t forget to change it back when you are finished.

Tip  If you need to restrict a fetch request to a subset of the managed objects stored in the persistent store, use a predicate. There’s an entire chapter dedicated to predicates in Learn Objective-C on the Mac, 2nd Edition by Mark Dalrymple, Scott Knaster, and Waqar Malik (Apress, 2012). The default template does not use predicates, but you’ll be working with them in the next several chapters.

Now you create an NSFetchedResultsController using your fetch request and context. You’ll learn about the third and fourth parameters, sectionNameKeyPath and cacheName, in Chapter 3.

// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController =     [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest                                         managedObjectContext:self.managedObjectContext                                           sectionNameKeyPath:nil                                                    cacheName:@"Master"];

Next, you set self as the delegate and set fetchedResultsController to the fetched results controller you just created.

aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;

Finally, you perform the fetch and, if there are no errors, you assign the results to your private instance variable _fetchedResultsController and return the results.

NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
    // Replace this implementation with code to handle the error appropriately.
    // abort() causes the application to generate a crash log and terminate. You should not use this
    // function in a shipping application, although it may be useful during development.
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}
return _fetchedResultsController;

Don’t worry too much about the details here. Try to get your head around the big picture. As you make your way through the next few chapters, the details will come into focus.

The Fetched Results Controller Delegate Methods

The fetched results controller must have a delegate, and that delegate must provide four methods, which you will describe in the pages that follow. These four methods are defined in the protocol NSFetchedResultsControllerDelegate. The fetched results controller monitors its managed object context and calls its delegates as changes are made to its context.

Will Change Content Delegate Method

When the fetched results controller observes a change that affects it—such as an object it manages being deleted or changed, or when a new object is inserted that meets the criteria of the fetched results controller’s fetch request—the fetched results controller will notify its delegate before it makes any changes, using the method controllerWillChangeContent:.

The vast majority of the time a fetched results controller will be used along with a table view, and all you need to do in that delegate method is to inform the table view that updates about to be made might impact what it is displaying. This is the method that ensures it gets done:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView beginUpdates];
}

Did Change Contents Delegate Method

After the fetched results controller makes its changes, it will then notify its delegate using the method controllerDidChangeContent:. At that time, if you’re using a table view (and you almost certainly will be), you need to tell the table view that the updates you told it were coming in controllerWillChangeContent: are now complete. This is handled for you like so:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
}

Did Change Object Delegate Method

When the fetched results controller notices a change to a specific object, it will notify its delegate using the method controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:. This method is where you need to handle updating, inserting, deleting, or moving rows in your table view to reflect whatever change was made to the objects managed by the fetched results controller. Here is the template implementation of the delegate method that will take care of updating the table view for you:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath]
             withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath]
             withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:@[indexPath]
             withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:@[newIndexPath]
             withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

Most of this code is fairly straightforward. If a row has been inserted, you receive a type of NSFetchedResultsChangeInsert and you insert a new row into the table. If a row was deleted, you receive a type of NSFetchedResultsChangeDelete and you delete the corresponding row in the table. When you get a type of NSFetchedResultsChangeUpdate, it means that an object was changed and the code calls configureCell to ensure that you are looking at the right data. If a type of NSFetchedResultsChangeMove was received, you know that a row was moved, so you delete it from the old location and insert it at the location specified by newIndexPath.

Did Change Section Delegate Method

Lastly, if a change to an object affects the number of sections in the table, the fetched results controller will call the delegate method controller:didChangeSection:atIndex:forChangeType:. If you specify a sectionNameKeyPath when you create your fetched results controller, you need to implement this delegate method to take care of adding and deleting sections from the table as needed. If you don’t, you will get runtime errors when the number of sections in the table doesn’t match the number of sections in the fetched results controller. Here is the template’s standard implementation of that delegate method that should work for most situations:

- (void)controller:(NSFetchedResultsController *)controller
 didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
 atIndex:(NSUInteger)sectionIndex
 forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
      withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
       withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

Using these four delegate methods, when you add a new managed object, the fetched results controller will detect that, and your table will be updated automatically. If you delete or change an object, the controller will detect that, too. Any change that affects the fetched results controller will automatically trigger an appropriate update to the table view, including properly animating the process. This means that you don’t need to litter your code with calls to reloadData every time you make a change that might impact your dataset. Very nice!

Retrieving a Managed Object From the Fetched Results Controller

Your table view delegate methods have become much shorter and more straightforward, since your fetched results controller does much of the work that you previously did in those methods. For example, to retrieve the object that corresponds to a particular cell, which you often need to do in tableView:cellForRowAtIndexPath: and tableView:didSelectRowAtIndexPath:, you can just call objectAtIndexPath: on the fetched results controller and pass in the indexPath parameter, and it will return the correct object.

NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];

Creating and Inserting a New Managed Object

From the function menu in the editor pane, select insertNewObject, which is the method that is called when the + button is pressed in the sample application. It’s a nice, simple example of how to create a new managed object, insert it into a managed object context, and then save it to the persistent store.

- (void)insertNewObject:(id)sender
{
    NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
    NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
    NSManagedObject *newManagedObject =         [NSEntityDescription insertNewObjectForEntityForName:[entity name]                                       inManagedObjectContext:context];
    // If appropriate, configure the new managed object.
    // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
    [newManagedObject setValue:[NSDate date] forKey:@"timeStamp"];
    // Save the context.
    NSError *error = nil;
    if (![context save:&error]) {
         // Replace this implementation with code to handle the error appropriately.
         // abort() causes the application to generate a crash log and terminate.         // You should not use this function in a shipping application,          // although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}

Notice that the first thing the code does is to retrieve a managed object context from the fetched results controller. In this simple example where there’s only one context, you could also have retrieved the same context from the application delegate. There are a few reasons why the default code uses the context from the fetched results controller. First of all, you already have a method that returns the fetched results controller, so you can get to the context in just one line of code.

NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];

More importantly, though, a fetched results controller always knows which context its managed objects are contained by, so even if you decide to create an application with multiple contexts, you’ll be sure that you’re using the correct context if you pull it from the fetched results controller.

Just as you did when you created a fetch request, when inserting a new object, you need to create an entity description to tell Core Data which kind of entity you want to create an instance of. The fetched results controller also knows what entity the objects it manages are, so you can just ask it for that information.

NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];

Then it’s simply a matter of using a class method on NSEntityDescription to create the new object and insert it into a context.

NSManagedObject *newManagedObject =     [NSEntityDescription insertNewObjectForEntityForName:[entity name]                                   inManagedObjectContext:context];

It does seem a little odd that you use a class method on NSEntityDescription, rather than an instance method on the context you want to insert the new object into, but that’s the way it’s done.

Though this managed object has now been inserted into the context, it still exists in the persistent store. In order to insert it from the persistent store, you must save the context, which is what happens next in this method:

// Save the context.
NSError *error = nil;
if (![context save:&error]) {
    // Replace this implementation with code to handle the error appropriately.
    // abort() causes the application to generate a crash log and terminate.     // You should not use this function in a shipping application,     // although it may be useful during development.
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

As the comment says, you need to handle the error more appropriately than calling abort. We’ll cover this more in the ensuing chapters. Also, notice that you don’t call reloadData on your table view. The fetched results controller will realize that you’ve inserted a new object that meets its criteria and will call the delegate method, which will automatically reload the table.

Deleteing Managed Objects

Deleting managed objects is pretty easy when using a fetched results controller. Use the function menu to navigate to the method called tableView:commitEditingStyle:forRowAtIndexPath:. That method should look like this:

- (void)tableView:(UITableView *)tableView         commitEditingStyle:(UITableViewCellEditingStyle)editingStyle         forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
        [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
        NSError *error = nil;
        if (![context save:&error]) {
             // Replace this implementation with code to handle the error appropriately.
             // abort() causes the application to generate a crash log and terminate.              // You should not use this function in a shipping application,              // although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

The method first makes sure that you’re in a delete transaction (remember that this same method is used for deletes and inserts).

if (editingStyle == UITableViewCellEditingStyleDelete) {

Next, you retrieve the context.

NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];

Then the context is asked to delete that object.

[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

Next, the managed object context’s save: method is called to cause that change to be committed to the persistent store.

NSError *error = nil;
if (![context save:&error]) {
    // Replace this implementation with code to handle the error appropriately.
    // abort() causes the application to generate a crash log and terminate. You should not use this
    // function in a shipping application, although it may be useful during development.
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

No need to admonish you again about the call to abort, as we discussed this previously.

And that’s all there is to deleting managed objects.

Putting Everything in Context

At this point, you should have a pretty good handle on the basics of using Core Data. You’ve learned about the architecture of a Core Data application and the process of using entities and properties. You’ve seen how the persistent store, managed object model, and managed object context are created by your application delegate. You learned how to use the data model editor to build entities that can be used in your program to create managed objects. You also learned how to retrieve, insert, and delete data from the persistent store.

Enough with the theory! Let’s move on and build us some Core Data applications, shall we?

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

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