Learning Core Data for iOS: Your First Core Data Application
- If you can’t explain it simply, you don’t understand it well enough.
- Albert Einstein
Kinesthetic learning, or learning by doing, is one of the best ways to absorb and retain information. The topic of Core Data has been a great hurdle for many seasoned programmers, so it’s about time a book with a hands-on approach to Core Data was written. In order to avoid side tracking into deep topics too early, this chapter has many pointers to later chapters. First things first: It will give you a Core Data essentials primer, then dive right in and show how to add Core Data to the sample application. The sample application will be expanded over the course of this book as increasingly advanced topics are introduced.
What Is Core Data?
Core Data is a framework that enables you to work with your data as objects, regardless of how they’re persisted to disk. This is useful to you as an Objective-C programmer, because you should be comfortable using objects in code already. To provide data objects, known as managed objects, Core Data sits between your application and a persistent store, which is the generic term given to a data file such as an SQLite database, XML file (which can’t be used as a persistent store on iOS), or Binary (atomic) store. These files are called “persistent” because they can survive the underlying hardware being reset. Another (oddly named) persistent store option is the In-Memory store. Although it isn’t really “persistent,” an In-Memory store allows you to leverage all the functional benefits of Core Data to manage your data, such as change management and validation, not to mention performance.
To map data to a persistent store from managed objects, Core Data uses a managed object model, where you configure your application’s data structure using an object graph. You can think of an object graph as a collection of cookie cutters used to make managed objects from. The “object” in object graph refers to something called an entity, which is used as a cookie cutter to make a customized managed object. Once you have managed objects, you’re then free to manipulate them natively in Objective-C, without having to write any SQL code (assuming you’re using SQLite as the persistent store, which is the most common scenario). Core Data will transparently map those objects back to a persistent store when you save to disk.
A managed object holds a copy of data from a persistent store. If you use a database as a persistent store, then a managed object might represent data from a table row in that database. If you use an XML file as a persistent store (Mac only), then a managed object would represent data found within certain data elements. A managed object can be an instance of NSManagedObject; however, it’s usually an instance of a subclass of NSManagedObject. This is discussed in detail in Chapter 2, “Managed Object Model Basics.”
All managed objects exist in a managed object context. A managed object context exists in high-speed volatile memory, also known as RAM. One reason a managed object context is required is the overhead involved with transferring data to and from disk. Disk is much slower than RAM, so you don’t want to use it more than necessary. Having a managed object context allows access to data that has been previously retrieved from disk to be very fast. The downside, however, is that you need to call save: on the managed object context periodically to write changes back to disk. The managed object context exists also to track changes to its objects in order to provide full undo and redo support.
To help visualize how the main pieces of Core Data fit together, examine Figure 1.1.
Figure 1.1 Core Data overview
Persistent Store Coordinator
On the left of Figure 1.1, a persistent store coordinator is shown containing a persistent store with table rows. When you set up a persistent store coordinator, you’ll commonly choose an SQLite database as the persistent store. Other options for the persistent store are Binary, XML, and In-Memory stores. The thing to note about Binary and XML stores is that they are atomic. This means that even if you only want to change a small amount of data, you still have to write out the whole file to disk when you save. Of course, the same issue applies when reading an atomic store into memory in the first place. This can become problematic if you have a lot of data because it consumes valuable memory.
An SQLite database, on the other hand, is updated incrementally as change logs, also known as transaction logs, are committed. As a result, the SQLite database memory footprint is comparably very small. For these reasons, you’ll typically choose an SQLite database, especially when integrating Core Data with iCloud.
A persistent store coordinator can have multiple persistent stores. One situation where this may be appropriate is when Core Data is integrated with iCloud. By putting data that doesn’t belong in iCloud into one store, and data that does in another, you will save network bandwidth and iCloud storage space. Even though you would then have two persistent stores, it does not mean that you need two separate object graphs. Using Core Data model configurations allows you to use separate stores, yet still have the one object graph. When you set up a Core Data model configuration, you can select what parts of the object graph belong in what persistent store. If you do use separate persistent stores, you’ll need to ensure there’s no requirement for a relationship between data in each store. Core Data configurations are discussed in Chapter 15, “Taming iCloud.”
A persistent store is created from an instance of NSPersistentStore and a persistent store coordinator is created from an instance of NSPersistentStoreCoordinator.
Managed Object Model
In the middle of Figure 1.1, a managed object model is shown sitting between a persistent store coordinator and a managed object context. As its name suggests, a managed object model is the model or graphical representation of a data structure. It forms the basis on which managed objects are produced. This is similar to a database schema and is also referred to as an object graph. To create one, you’ll use Xcode to configure entities and the relationships between them. An entity is similar to a table schema in a database. Entities don’t contain data; they only dictate the properties that managed objects that are based on them will have. They’re cookie cutters! Just as a database table has fields, similarly an entity has attributes. An attribute can have one of several data types, such as integer, string, or date. Chapter 2 and Chapter 4, “Managed Object Model Expansion,” cover these topics in more detail.
A managed object model is created from an instance of NSManagedObjectModel.
Managed Object Context
On the right of Figure 1.1, a managed object context is shown with managed objects inside. A managed object context manages the lifecycle of objects within and provides powerful features such as faulting, change tracking, and validation. Faulting simply means that when you fetch data from a persistent store, only the parts you need are retrieved. Faulting is covered further in Chapter 10, “Performance.” Change tracking is used for undo and redo support. Validation is the enforcement of rules set in the managed object model. For example, a minimum or maximum value rule can be enforced at an attribute level on an entity. Validation is discussed in Chapter 2.
Much like you can have multiple persistent stores, you may also have more than one managed object context. Typically you would use multiple contexts for background processing, such as saving to disk or importing data. When you call save: on a foreground context, you may notice user interface lag, especially when there are a lot of changes. An easy way to get around this issue is to simply call save: only when the home button is pressed and the application enters the background. Another more complicated yet flexible way is to use two managed object contexts. Remember that a managed object context is an area in high-speed memory. Well, you can actually configure a managed object context to save to another managed object context. Once you save a foreground context to a background context, you may then save the background context to disk asynchronously. This staged approach ensures the writes to disk never interfere with the user interface responsiveness.
The ability to configure a parent and child context hierarchy has been available since iOS 5. A child context treats its parent as a persistent store, when really the parent is another context that exists to process heavy workloads, such as saving in the background. This is discussed in further detail in Chapter 11, “Background Processing.”
A managed object context is created from an instance of NSManagedObjectContext.