A Quick Overview of Basic CoreData Concepts
Somebody asked me today about how to go about creating a Cocoa app which allows Rails-style object-relational mapping. In other words, how do you model a schema in a data store without having to re-model all of it in code? This is exactly what Core Data is designed for.
I think the
Core Data Overview tutorial does a pretty good job of laying out the landscape, and I've mentioned some things about Rails and Cocoa before, but it seems like it might be helpful to share a quick cheat sheet reference.
Schema Classes
The Core Data API is effectively split up into schema classes and runtime classes. On the schema side, the
NSManagedObjectModel is at the top of the object graph. It contains the complete representation of the schema (
though you can have multiple models in your application bundle).
An
NSEntityDescription: Essentially equivalent to a table in a SQL database. A good example of an entity might be a Person, an Image, an Invoice, and so on. An entity contains multiple instances of
NSPropertyDescription, which can be one of the following:
- NSAttributeDescription: Typically a simple scalar value — string, number, date or simply binary data.
- NSRelationshipDescription: A reference to another object in the object graph, such as another Person, Image, Invoice, and so on.
- NSFetchedPropertyDescription: A dynamic list of objects that are returned based on a fetch with specific search criteria
Although you can do all of this in code, it's generally easier to design your schema using the Xcode modeler, in which case all of this work is done for you behind the scenes. And Xcode is free, so why not?
Runtime Classes
On the runtime side, the
NSManagedObjectContext contains all of your data objects, tracks changes to them, and is responsible for initiating the save process when the user chooses File → Save. You generally want one managed object context per thread.
Every instance of a data object, such as a Person, exists as an instance of
NSManagedObject. Each managed object is linked to an NSEntityDescription to describe which property keys it can store data for. When you want to set a value on a managed object, you do something like:
[person setValue:@"Jude" forKey:@"firstName"];
If you define custom accessors on your NSManagedObject subclass, you could also do these sorts of things:
[person setFirstName:@"Jude"];
And with a bit of Objective-C 2.0 fanciness, you can do this:
person.firstName = @"Jude";
Custom accessors are especially helpful for non-object types, such as float, int, and so on (again, this is Objective-C 2.0 syntax):
person.heightInFeet = 6.1;
NSLog(@"person height: %f", person.heightInFeet);
(At
WWDC 2007,
Session 203 is an introduction to the new features of Objective-C 2.0, and Session 222 dives into more advanced features.)
After the user has made changes to the in-memory data, they will activate a menu item to save the file (
or the application might do this periodically itself), which eventually triggers a -save: message in the context. This eventually filters down to the
NSPersistentStoreCoordinator, which is responsible for making sure the data is written to the specific store that the objects belong to.
The persistent store coordinator can have multiple active stores (
essentially, individual data files on disk) at the same time, and present them to the application as a single store with a single set of objects. This is useful if you have an application which stores a lot of data from multiple sources, and you may want to dynamically load and unload storage locations.
In Tiger, the store API is private, and only supports SQLite, XML and binary. In Leopard, you can create your own store types. This is covered in Session 105 next week,
Optimizing Your Core Data Application.
All of these sessions, of course, are listed on the
WWDC 2007 session page.
Compared to Rails
If you are used to working with Rails, Core Data will probably seem like a parallel universe, very similar to your own. Many of the basic concepts are there, though the particular angles you come at things might be a bit different.
In Rails (and Ruby in general), it's accepted and actually encouraged to use language tricks to reduce the total number of lines of code. In Core Data and Cocoa, the idea is to simplify and reduce to the total amount of code (this, after all, what Cocoa Bindings is about) — but it's also important to not get too clever for your own good.
Objective-C gives you a lot of power to do some crazy things. But the first goal is readability, which is not necessarily the same as the fewest lines of code. Remember that other people (including a future version of yourself) will likely want to read this code some day. Creating shortcuts is fine, but culturally, Cocoa discourages the creation of application-specific metalanguages. This is probably partially why there's so little special syntax in Objective-C.
In short, though, a NSManagedObject is essentially equivalent to an ActiveRecord instance. Many of the same rules apply: you get basic behavior for free based on the schema definition, but you can customize the class as well. Leopard supports migrations in Core Data, which will be described in session 111 at WWDC next week.
The other major conceptual difference is the idea of NSManagedObjects being in a state of
changed but
not saved. This simply comes down to the way a desktop app tends to work versus a web app, and actually caught me off guard initially in Rails.
The context accumulates changes — which can be backed out and redone with with Undo and Redo — but they don't actually end up on disk until -save: is called and the data is passed off to the persistent store coordinator.
Don't Stress
Fortunately, you don't need to memorize all of this to use Core Data. There's a very gentle introduction at Cocoa Dev Central called
Build a Core Data Application. It's entirely visual — there's no code to write. You can use that as a starting point and customize with your own code as necessary.