Core Data provides an easy to use wrapper
around SQL that lets you spend your time thinking
in objects instead of queries.
What you will do
You are going to use Core Data to create
and use the persistent data in an application.
You are going to build an application
called ’Conference’. This application will help users manage which
sessions they want to attend at a conference. For each track in the conference
you will display a list of sessions in that track. Users then select
the sessions they want to attend.
The Conference application has four screens. The opening screen displays a list of tracks. The user can add and
delete tracks and when the edit button is pushed selecting a track takes
you to the editing screen for the track information.
The list of tracks displayed here comes from Core Data, specifically the managed object context. All the Track objects are fetched from persistent
storage and that list becomes the underlying data model for this table.
Tracks can be added to the list by clicking on the ’+’ button in the top
left hand side. When that button is clicked you will call on Core Data
to make the new object and place it into the managed object context.
Clicking any of the red ’-’ buttons on one of the rows will allow the
user to delete the object. If an object is deleted, you will remove it from your managed object context. After the changes are made
as you’d expect you persist them by calling the save: on the managed
object context.
The next screen allows the user to edit the track’s name and abstract.
As the text of the name or abstract for a track is edited the track’s
properties are updated. These changes too are registered by the managed
object context. You don’t have to code that, the managed object
context is watching your objects and makes note of any changes. When
the user finishes here the changes are persisted again.
When the track list is not in editing mode, selecting a track takes the
user to the list of sessions for that track.
Pressing the ’+’ button creates a new Session and adds
it to the managed object context.
And finally selecting a particular session takes you to the editing screen
for that track.
When the user is finished making changes to the session information
the text is placed back on the session object, and the
managed object context makes note of the changes since its watching
the session object. In fact the MOC is watching all objects that are in
it. Any change you make to any object that you get from an MOC are
catalogued by the MOC so they can be persisted when the save: method is called.
// Core Data Exercise
Understanding the Core Data Stack
Open Xcode:
Create a new Project (⌘+⇧+N). Make it a Navigation-based
Application
Check the
box that says Use Core Data for storage and name it Conference
Look at the application delegate
and see what’s been added to our familiar template.
For starters the app delegate class (ConferenceAppDelegate) has quite
a bit more to it than we have seen in the past. While you have typically
seen a window and viewController of one sort or another in this class
there are several new properties related to Core Data. Here is the
header file:
This set of four new properties (managedObjectModel,managedObjectContext,persistentStoreCoordinator,navigationController) work together in what is called the Core
Data Stack. This stack of objects is the basis of how Core Data works.
These are the objects that give Core Data its really cool feature set. The
four objects that make up this stack are the persistent object store,
the persistent store coordinator, the managed object context and the
managed object model.
The persistent object store (or POS) performs all the lowest level translation
of object speak to data as well as managing the opening and closing
the underlying file. It is created, managed and heavily used by the
persistent store coordinator. Consider the SQLite POS as an example,
objects go into the POS and SQL comes out and is pushed through the
SQLite API and a change to the database file results. When objects are
needed, a request comes in from the persistent store coordinator and is
translated by the POS to SQL queries. The SQL is then sent through the
SQLite API and the returned records are used by the persistent store
coordinator to produce objects.
Core Data ships with three POS implementations, SQLite, binary and
memory. The SQLite implementation is the easiest to debug and see what is actually persisting. The binary
implementation is good for putting really small data sets into if you
don’t want the performance overhead of SQL and your data is very simple.
The memory implementation is good for temporary data that needs
some of the other features of Core Data (like undo support) but does
not need to persist beyond the POS.
The persistent store coordinator (or PSC for short) provides a generalized
cover over the persistent object store. In the most general case you can
have multiple object stores but on the phone you rarely will. Where the
persistent object store is specific to a particular store, the persistent
store coordinator is more general. The persistent store coordinator uses
the managed object model to help it understand the form and layout of
the objects that are being persisted to the stores.
The managed object model (or MOM for short) contains the description,
or the meta-data, of your model. It is where you describe the entities
and their properties that make up the model of your application. In
your application you are going to have two entities, Session and Track.
The last object in the stack, and the one will be interacting with the
most, is the managed object context (or MOC for short). The MOC is
used as a scratch pad. Objects are pulled through the stack into the
MOC and then kept there while you change them. All inserts, deletes and
updates to the set of objects in the MOC are held until we tell the MOC
to save. At that point the MOC’s list of changes is pushed down through
the stack, at each step translated closer to the eventual language of the
POS where it eventually becomes native (i.e. SQL statements for the
SQLite POS) and sent to the persistent storage.
This stack of objects is created for you by the app delegate. Thankfully
all the code to build out the stack is generated for you when you check
that little ’Use Core Data for storage’ checkbox.
Starting on the bottom of the stack with the persistent store coordinator
and persistent object store. Here is the code in the App Delegate:
First you grab the documents
directory and add ’Conference.sqlite’ to the end, you use that
path to create a URL for our database file.
Next you create an instance
of NSPersistentStoreCoordinator with the managed object model.
And finally you configure a new persistent object store
The persistent object store uses the SQLite type and stores the
database in the file specified by storeURL. The last argument is a pointer
to an error, if something goes wrong then error will be set to an instance
of NSError that contains detailed information about what went wrong.
The applicationDocumentsDirectory property is used to find the application’s
Documents directory. It is calculated every time the get method
is called. Here is the implementation.
The managedObjectModel method grabs all the model
files that are in your application’s bundle and merges them into one MOM.
This class method looks through the whole application bundle looking
for model files, each one is loaded and merged into the overall MOM. In
practice though most iPhone applications will have only one model.
Roughly speaking
each entity corresponds to a table in the database and each attribute
corresponds to a column in that table. On the Objective-C side each
entity corresponds to a class and each attribute corresponds to a property
on that class. There is of course a ton of detail behind how a
MOM is used to map objects into rows and vice-versa but you don’t
have to fully understand all that thanks to the great tools available in
Xcode.
Double click on the Conference.
xcdatamodel in Xcode.
You are going to modify this template data model so it captures the
Track and Session entities.
To do this you need to:
Change the template provided Event entity to the Track entity.
Add the name and trackAbstract attributes and the sessions relationship
to the Track entity.
Create the Session entity.
Add the name, sessionAbstract and sessionID attributes and the track
relationship to the Session entity.
Generate the Track and Session classes to the project.
With these two entities and the relationship between the modeled, Core
Data will have the information it needs to make them persistent.
Select the entity then in
the Entity attributes inspector change the name from Event to Track.
Change the class name from NSManagedObject to Track.
Track has two attributes, name and trackAbstract. Both of them are strings
with no constraints (length, reg-ex etc). Add them by selecting the ’+’
button under the properties list (the list to the right of the entity list)
and choosing Add Attribute.
Turn off optional for each of the attributes. While this
is not necessary it will help to make sure that no bogus data makes
it into your database. As you have probably noticed there are lots of
other ways you can customize the constraints that Core Data will place
on your attributes. For example if you were to provide a Regular Expression
(also know as a regex) in the attribute inspector Core Data would
ensure that whatever value was placed in that attribute matches the
regex before it would save the value. If the value fails any of the constraints
a validation error is raised. You can use the error to create a
user visible/understandable message to show so the user knows what
to fix.
Create the Session entity.
Under the list of entities (where Track currently shows up on the top
left side), click the ’+’ button. Rename the new entity to Session and
add three string attributes, sessionID, name and sessionAbstract. Again all
of the Session attributes should have no constraints and the Optional
switch should be turned off.
Select the Track entity and click the ’+’ button
under the property list (top right hand side). Choose Add Relationship
and name the new relationship sessions
Make the relationship’s destination
the Session entity
choose the to-many checkbox and make sure
the delete rule is Cascade
Add the inverse relationship to the Session entity. Select the Session entity and add a relatationship,
name it track, set it’s destination to Track and choose its inverse to
be sessions. Set the delete rule on the new relationship to Nullify and
turn Optional off.
Generate the classes that will be used to represent
this model in your application. In Xcode select the Classes group. Click
on the group and choose Add>New File... menu item. When the
dialog box pops up choose Managed Object Class:
Click next on the next page of the dialog, you don’t need to change
anything here but make sure the Conference target is selected. On the
final page of the dialog, choose the checkbox next to Session and Target
to have the generator use both our entities when it makes the classes: You should have a header and implementation
file for both Track and Session classes.
Created a new group in
Xcode called Model Classes
then move the model classes into that group.
The Sessions.h file contains nothing you haven't seen before, but the implementation file contains the @dynamic property:
This declaration says to the
compiler that the properties will have get/set method pairs provided for
them at run time so the compiler does not need to provide them. Core
Data provides these methods for you so you don’t have to worry about
them.
Look at the The CoreDataGeneratedAccessors category
in the Track.h file. An
Objective-C category is a way for you to add methods to an object. All the methods declared in the CoreDataGeneratedAccessors category (until the @end) become
part of the interface.
The methods that are declared in the CoreDataGeneratedAccessors
category are generated by Core Data at runtime (thus
the name of the category). You only need the category in the header file
so that you can call the methods without a compiler warning. Just know that you can call addSessionsObject: on any instance of Track.
Table Views ask
their data source for the number of sections, the number of rows in
each section and then for the cell in a particular location.
What is new is the adaptor that sits between Core Data and the table view data
source API. The class that provides that adaptation is NSFetchedResultsController
(or FRC for short).
You'll find the The first is numberOfSectionsInTableView: in the RootViewController.m file
The fetched results controller knows how many sections are in the data
that its managing so it is easy for it to return that list.
The tableView:numberOfRowsInSection:
method returns the row count for the particular section.
The tableView:cellForRowAtIndexPath: method is called to get the cell, configured
and ready to display. And again the fetched results controller
comes to our aid with the objectAtIndexPath: method.
The first method in this code block does the cell configuration by setting
the textLabel’s text to the name of the track and setting the detailTextLabel’s text to the trackAbstract. The cell configuration was pulled out
into a separate method because you need to call it from two places. The
first one is the tableView:cellForRowAtIndexPath:
The fetchedResultsController code does three things