|Tim Isted, Tom Harrington|
Published by Addison-Wesley Professional
In This Chapter
- A Little History
- Why Use Core Data on iOS?
- Core Data iOS and Desktop Differences
- Core Data Case Studies
Mac OS X 10.4 Tiger introduced Core Data to provide a unified framework for storing and fetching an application’s model data. Further enhanced under Mac OS X 10.5 Leopard, Apple subsequently made it available for use on iOS devices from iPhone OS 3.0 onward. The Core Data framework certainly alleviates most of the hassle of working with model data, but it can also seem to have a pretty steep learning curve.
This overview begins with a brief history of Core Data’s development, before looking at a high-level overview of the framework itself. The final section of this chapter looks at some real-world examples of how iOS developers have leveraged the power of Core Data to simplify the model handling in their applications.
A Little History
A large portion of what is now Core Data previously existed as the Enterprise Objects Framework (EOF). Created by NeXT, as part of WebObjects, EOF is used for accessing data held in relational database systems. To save developers from having to write lots of low-level database-access code, EOF provides a mechanism for accessing the data as an object-oriented class structure, using object-relational mapping.
Instead of having to write code to talk to a database and ask it for rows of database tables and columns, object-relational mapping “maps” those tables into classes, the columns into class attributes, and the rows into class instances. This makes it possible to change the underlying database or storage mechanism beneath the classes without worrying about changing any code that accesses those classes, since EOF handles everything behind the scenes.
The Birth of Core Data
Much of the functionality behind Core Data later “grew” out of EOF, but it is important to emphasize that Core Data is neither a database in itself, nor a database-access framework. Instead, Core Data is a complete data model solution allowing visual design of an object graph, code to create and query objects in that graph, and code to persist the objects to disk.
Although it is certainly common to use Core Data to store objects in an SQL database (SQLite to be precise), it’s equally possible to use non-database storage such as XML files, binary files, or even completely custom persistent stores created by developers to suit their specific needs. On the desktop, Apple provides SQLite, XML, and binary storage options. Under iOS, there’s no XML store, and most iOS applications end up using SQLite stores.
It’s also important to emphasize that a Core Data-backed model can be used to store all kinds of data. It doesn’t necessarily have to be data that would traditionally be thought of as database-style data, like patient records for a doctor’s surgery, or invoices and financial information for a company accounts application.
It’s just as easy to use Core Data to store vector graphics information for an iOS drawing application, account information and search terms for a social networking app, or high scores and persistent state information for a game.
Why Use Core Data on iOS?
The Core Data framework provides a tried and tested, very fast means of accessing model data. It doesn’t need to cost much in terms of memory or processor usage, and it bundles up a great deal of very useful functionality that we get for free.
Because of its heritage as an aid to working with relational database systems, one of Core Data’s main selling points is its ability to manage relationships between the objects it maintains, such as patients belonging to a doctor in a surgery. Using the visual data modeler, you define the links between objects using terms like one-to-one, one-to-many, and many-to-many. The framework is designed to use inverse relationships wherever possible, so it is extremely easy to maintain “referential integrity” among your data.
The relationships you define can even be given rules, such as requiring that a particular relationship is always defined (for example a financial transaction must be linked to a bank account), or specifying that a company must have at least one director. When it comes time to save, or persist, new objects into the underlying store, the framework refuses to accept objects that don’t obey the required rules, thereby maintaining the integrity of the object graph.
Similarly, if you change one side of a relationship, Core Data automatically handles the relevant changes on the other side. If you transferred a patient from one doctor to another, for example, each doctor’s relevant patient list would automatically be updated accordingly. Likewise, if you deleted a bank account from a financial application, all the relevant transactions would automatically be deleted as well, provided the relationship rules were set up correctly in the data model.
Managed Objects and Data Validation
When you work with objects backed by a Core Data store, you work with what are called managed objects. The standard managed object functionality encompasses some of the relationship handling mentioned earlier, but it also allows for data validation.
Validation of objects happens on the attributes, or properties, of those objects. You can either set per-property rules in the model itself, such as:
- a person’s salary can’t be less than zero
or use custom validation rules to enforce rules between multiple properties, such as:
- a patient couldn’t have any children if they were younger than a certain age.
As noted earlier, if you try to update an object with values that don’t pass the validation tests, Core Data will complain and prevent you from saving incorrect data.
Undo and State Management
Core Data will also automatically maintain an undo stack, if required, which means that changes to objects can be undone even after they’ve been saved to the persistent store. Again, this is handled automatically for you so you don’t have to worry about writing undo management code yourself.
So, the case for using Core Data is extremely strong. And, because it’s exactly the same underlying framework code on both platforms, once you’ve learned how it works under iOS, you already know a great deal about how it works on the desktop! Having said this, there are a few differences in practice between working with Core Data on iOS as opposed to the Mac desktop.
Core Data iOS and Desktop Differences
One major difference between desktop and iOS Core Data support is that there is currently no support for Bindings on iOS. On the desktop, Bindings use Key-Value-Observing (KVO) and Key-Value-Coding (KVC) to maintain links between user interface items and model objects or attributes. With Bindings, it’s possible to build an impressive Core Data application on the desktop without writing any code at all—you simply use Xcode 4’s Interface Editor (the separate Interface Builder application under Xcode 3) to Bind interface items like table views and text fields to groups of managed objects fetched automatically from the Core Data store by object and array controllers.
On iOS, this instant gratification style of Core-Data-with-Bindings development is not possible; instead you need to work directly with various parts of the Core Data framework to supply the relevant information to your user interface.
The Fetched Results Controller
To aid in this process, Apple offers a class new to Core Data and unique to iOS, called
NSFetchedResultsController. As its name implies,
NSFetchedResultsController (which has an entire chapter devoted to its coverage, Chapter 5) is used as a controller-layer class to help in the interaction between a view and the data fetched from the persistent store. It is designed primarily to function as a data source for a
UITableView, governing how many rows and sections the view should display, and providing the contents for each row.
The fetched results controller works in the most efficient way possible to eliminate the need to load all the fetched objects into memory at once, and automatically disposes of objects not currently being accessed. This makes working with large numbers of objects much easier.
If you use a fetched results controller to display a collection of several thousand objects in a table view, for example, only a handful of those objects will ever be loaded into memory at any given time.
Core Data Case Studies
MoneyWell for iPhone
MoneyWell is a personal finance application designed to make the process of tracking expenses and managing cash flow obvious. It uses the envelope-budgeting method in the form of onscreen buckets to hold transactions, which allow the user to manage available cash proactively by seeing exactly what is available in any income or expense category.
When MoneyWell was being designed, Mac OS X Tiger and Core Data were brand new. The data relationships in MoneyWell are fairly complex, but Core Data simplified both schema design and the development process. When Apple announced an SDK for the iPhone, it was obvious that MoneyWell would be a perfect app to have in your pocket. The problem was that Core Data was not available to developers in that first release. We actually delayed development of MoneyWell for iPhone, assuming Apple would migrate Core Data to their device platform. That turned out to be a smart decision because Apple did include Core Data in the 3.0 SDK.
This solved so many development problems. Creating the model layer in MoneyWell for iPhone was a simple matter of copying the Xcode data model into the new project. Additionally, both the Mac and iOS versions of MoneyWell share the same data files allowing us to use all our existing test documents. We also benefited from Core Data being a first class framework that has been retooled extensively for performance on the iOS platform. Things like data access, data persistence, and object graph manipulation were trivial throughout the development process, allowing us to concentrate on solving more important problems like creating a good user experience. One particularly big win for us was the use of Core Data in conjunction with
UITableView. As one might imagine, a typical user can have years’ worth of financial data that could easily cripple the memory space on a mobile device. Without Core Data, we would have been forced to write complex paging algorithms or extensive SQLite queries, but instead we are able to query for a set of transactions and rely on Core Data’s ability to page data intelligently in and out of memory as needed.
Using Core Data easily saved us five or more months of development time.
—Kevin Hoctor and Michael Fey, No Thirst Software LLC
Calcuccino is a programmers’ calculator that uses the iPhone’s menus and swipe gestures to improve on the conventional calculator. The result is a powerful calculator for programming, engineering, and scientific use, with big buttons for fast and reliable entry.
The decision to use Core Data for Calcuccino was not an obvious one. Calcuccino does not have any large data handling needs, but it does need to store a reasonable history of the user’s calculations so they can view them later, or re-use a previous result. Calcuccino also needs to keep state information for the current calculation so it can be restored should the user switch from the app and return sometime later. Before Core Data, both of these requirements would have been done using XML files to persist the information, but this can lead to large portions of code being written just to handle the XML itself.
Core Data offers a data model designer in Xcode, so this was used to map out the data structure—primarily a store of
HistoryStep entities for the history steps, and
OpStep entities for the state of the current calculation. For the calculation engine itself we define a
CalcValue class—this is the basic number type used throughout Calcuccino (analogous to Cocoa’s
NSNumber class, but with additions for integer size and format). All
CalcValue instances are stored in the Core Data store using instances of a
The data model for Calcuccino is clean and maps nicely to the requirements of the app. There is a
CalcStore class that handles the interface between the Objective-C classes of the app and calls to Core Data. It disappoints me that there is still mapping code to map between Objective-C classes and entities, but this is no different than the code needed to pack and unpack classes to and from XML. I suspect that we will move further away from raw
NSManagedObjects and more toward custom classes to reduce this mapping code. There is also a change of thinking needed to shift between Cocoa’s regular container classes (
NSDictionary) and accessing entities with predicates and fetch requests.
Given our experience of using Core Data now, the question would be “would we use Core Data again?”
The answer is yes, which confirms that we believe we have made the right decision. Interfacing between
UITableViews and Core Data with
NSFetchedResultsController is a joy, especially using attributes to group calculations automatically by day in the history view. Once we get a few more versions of Calcuccino under our belt and we have had to version control the database using Core Data’s versioning capabilities, we hope that we will continue to believe that we made the right decision!
—Andy Dean, Cambridge Coders Ltd
When I first started on the Associated Press mobile application, I knew that it was going to need to be a Core Data application. The amount of data we were working with was simply too voluminous to attempt to use any other type of persistence layer. In addition, based on our requirements of asynchronous data transfers, Core Data was an easy choice.
Being able to set up data parsers that feed directly into the Core Data model, and that could be saved on a background thread, was extremely helpful in making the application perform well for its users.
When we started designing the application, we knew that the interface to the server was going to be used for more than one application, so we designed it as a separate library. By designing the library to “merely” add data to the Core Data persistent store, we were able to provide a common, easily understood interface to any application that was built upon it. Each application only had to watch for changes in Core Data to know that the underlying data had changed. By doing so, we were able to keep the interface code between the application and the library to a bare minimum.
We have now been using this library design for over a year with virtually no issues. The library has been used in dozens of applications at this point and is performing very well. Had we used another persistence engine I suspect we would still be battling with performance issues and memory issues.
—Marcus S. Zarra, Zarra Studios LLC