This is part of an ongoing series of blog posts about developing iOS Apps. Today I want to talk about the wonder and majesty that is Core Data.
When I first started delving into the tutorials on Lynda.com and elsewhere, I very quickly made it to the point where I wanted to store data somehow in the application. The data storage tutorial on Lynda.com was very informative, walking you through how to leverage the built-in capability to store data in a SQLite database, walking you through construction of the database itself, implementation of the lower level C code that interacts with the database (including writing SQL statements), and then sort of providing you with an Objective-C wrapper to make it easier to work with the db. I was a little dismayed at how complex this turned out to be, but looking at a few books, I was seeing the same sort of basic procedures, so I thought "This is what it means to use data on iOS devices".
In truth, this *was* how it was to use data on iOS devices -- until Core Data.
I have a background in Java, and have built a few web applications using Web Objects, Apple's framework for rapid prototyping that never quite saw the widespread adoption it deserved. The way the programmer interacts with the database in Core Data is very similar to Web Objects, so it turned out to be very refreshing when I started getting into iOS programming and came upon some of the same techniques.
As an aside, I spent the Fall 2010 semester digging into Flex, which is incredibly similar to Java programming, and did alot of work setting up applications that talked to various data sources, including MySQL databases and XML feeds. I never liked how up close and personal Flex and PHP programmers have to get with databases. Writing SQL statements and iterating through arrays is tedious work to me. Because of the Model-View-Controller way in which Web Objects interacts with the database, it seemed very unnatural to me to have to deal with those things. Things should go into and come out of databases as objects, or collections of objects.
So how is Core Data different? Well, for one thing, say goodbye to writing SQL statements. Apple has very graciously abstracted that very tedious work away from us. Just as you don't have to know how to build or fix an engine to drive a car (although it certainly helps), you don't have to know anything about databases to use Core Data.
Instead, you now work with Entities, Attributes on entities, and Fetch Specifications. You now also have something called a Managed Object Context that handles all of the actual interaction. The basic workflow is:
- Design rows in your database visually, as objects. Your objects can also have attributes and relationships with other objects. Attributes are like columns in your database, while relationships are like relations. This comes in handy, for example, when you want to load large amounts of binary data, as you can take advantage of something called "Faulting" (as I'll talk about a little later)
- When you want to interact with objects in your database, you ask the Managed Object Context to fetch them for you. You can fetch either all of the objects or just some of them using a Fetch Specification, which is just an object you can set up to specify how to Fetch objects from the db.
- After the objects are fetched, they are held in memory, where you are free to alter, undo, and redo changes on them. When you are ready to commit the changes to the database, you ask the Managed Object Context to save the data.
The following image illustrates how objects are visually defined in the database:

In this example, I have created a photo object to hold metadata about a photo, and an image object to hold the actual binary image data. I then set up a reciprocal relationship between these two objects. The reason I did it this way is to take advantage of a process called "Faulting".
The basic idea behind Faulting is that memory is at a premium on mobile devices. The actual binary data that makes up an image takes up much more memory than the string data that is collected in the metadata attributes defined on the Photo object above. So wherever possible, we want to represent our object using the metadata object (such as displaying the title in a row in a table rather than using a thumbnail). We use faulting to control when the binary image data is displayed on the screen. Because there is a two-way relationship between the Photo Object and the Image Object, the Managed Object Context knows to only fire that fault when the image data is actually needed (by asking for the Photo.toImage value in the code). Then it removes the image from memory when it is no longer needed, freeing up valuable space.
So this to me is a much cleaner and more logical way to interact with a database. Working with objects also has the benefit of being much cleaner code that is much more tightly coupled to the IDE, enabling you to take advantage of advanced features of Xcode like code completion and refactoring.
There is one problem I ran into though: I found myself in the situation of needing to store an array of CGPoint values in the database. Core Data does not provide any collection data types. I searched around on the web and found that there are a few solutions to this, neither of which was particularly great.
One solution people do is to use a one-to-many relationship between objects, defining a collection object and then a to-many relationship to a series of more primitive objects like strings or whatever. Since I had to store perhaps thousands of points, I thought this might get a little messy, so my next option was to convert the array into binary data and store it as a binary data type. This took some effort to figure out, but I finally got it working by
- Converting my data from CGPoint objects into NSValue objects because NSValue objects respect NSCoding, which is a delegate protocol used to encode objects into binary data and decode it again (objects that are encoded have to implement the NSCoding protocol, which will be the subject of another post)
- Creating a custom SequencedMove object (also respecting NSCoding) to hold the NSValue point and a string representing the identity of the object that pertained to the point
- Adding each SequencedMove to an NSArray (which also respects NSCoding), then
- Encoding the NSArray into binary data.
Getting the data back again is the reverse of this process.
Recent Comments