On iOS, the two main storage technologies available to developers are Core Data and SQLite. Both technologies have advantages and disadvantages depending on the amount and type of data you need to store and manage. This article and accompanying sample application provide an overview of Core Data and SQLite, compares the two technologies, and provides sample code. The code is for an iPhone application that enables you to switch between Core Data and SQLite, thus enabling a direct comparison between these two technologies. Some simple metrics are displayed: memory usage, application CPU usage, and data store size for both the iPhone 4s and 5s. A screen shot of the application is show in Figure 1:
Figure 1.
Sample Data Set
The best way to compare Core Data and SQLite is to use the same data set when testing. While this is not a perfect approach (because a single data set cannot possibly illustrate all of the capabilities of Core Data and SQLite), for practical purposes, it is a reasonable approach. Our sample data set is a list of cars, car manufacturers, car type, and car details. For a particular car, we want to store the car type, details, and the manufacturer information. Table 1 illustrates this data set.
Car |
Type |
Details |
Manufacturer |
Pinto, $16,000 |
Compact |
AC, Automatic transmission |
Ford, Dearborn MI, 100,000 employees |
Tahoe, $45000 |
SUV |
Leather, Power Windows |
GM, Dearborn, MI, 100,00 employees |
Ferrari, $15000 |
Sports Car |
V12 |
Maranello, Italy |
Table 1.
If you don't like cars, feel free to substitute surfboards, as in Table 2:
Board |
Type |
Details |
Manufacturer |
Slinger, $850 |
High Performance |
6', single fin |
Infinity, Dana Point, CA 50 employees |
Table 2.
Core Data Primer
A quick primer on Core Data follows; however, to really understand Core Data, you should read Apple's Core Data Programming Guide and Dr. Dobb's careful explanation.
Core Data's focus is on objects rather than the traditional table-driven relational database approach. When storing data, you are actually storing an object's contents, where an object is represented by an Objective-C class that inherits the NSManagedObject
class. A typical application will have several objects used together, forming an object graph. For our sample data set, a car object contains car type, details, and manufacturer objects as members. Your application modifies the objects directly; when saving the objects, the NSManagedObjectContext
save method is called. Conversely, your application will fetch stored objects using the NSManagedObjectContext
object. Core Data handles all the details of saving the changes.
Figure 2 shows the main components of Core Data.
Figure 2.
The Data Model is where you define your data objects and their relations. This is done using the Data Model Editor, which is part of the XCode IDE. The data model file is stored locally (on your development system) as an XML file. However, when your application is built, this file is compiled in to a binary file (with a .momd extension) that is bundled with your iOS application. Each object is referred to as an "Entity," which contains one or more attributes. Don't get confused with the terminology here, an Entity is an object and an attribute is a member of your object. XCode will generate the source code (.m and .h files) for the classes defined in the Data Model Editor. To do so, select the data model and then the menu selection: Editor/Create NSManagedObject Subclass.
Figure 3 is a screen shot of the Data Model Editor in XCode.
Figure 3.
An important design consideration for your objects is the relationships between them. By "relationship" I am referring to instances when one object contains a reference to another. Is this a reference to many (one-to-many)? Or a reference each way to many (many-to-many)? In the sample application, the Car
object references a CarType
object where the CarType
object is also referenced by other Car
object instances. This is a one-to-many relationship between the CarType
and Car
object one CarType
can reference many Car
objects. This is similar in concept to a SQL foreign key and it is important to understand because any change to the CarType
record will affect all of the Car
objects. Figure 4 illustrates this relationship.
Figure 4.
The Persistent Store Coordinator (NSPersistentStoreCoordinator
) handles the details of the actual physical storage, whether the storage is a SQLite database, binary file, or iCloud. Your application doesn't know or care, it just works with the application objects directly. The nice thing about using Core Data is the ability to use these different storage types seamlessly. The Persistent Store Coordinator can handle different store instances. You might want to store frequently changing price data (such as gas prices) using iCloud, and store static data locally (such as gas station location). How you model, store, and manage you data should be driven by your application's requirements. The Persistent Store Coordinator uses the compiled data model file to determine the structure and organization of the objects being stored.
The Managed Object Context (NSManagedObjectContext
) works with the Persistent Store Coordinator to fetch, save, and track the collection of objects. These are powerful features, as the application does not have to track if one object in a collection has been modified or the details of storing the objects. The Managed Object Context also acts a scratch pad of sorts for your object collection. If your application makes changes to the objects and later needs to discard these changes, it's no problem the application can use the Undo Manager (NSUndoManager
) or simply reset the Managed Object Context (using the reset
method) and discard references to any of the objects.
There can be more than one Managed Object Context instance for a single store. For example, an application might use different contexts for different fetch results. As a result, an object instance can exist in both contexts simultaneously, potentially causing data inconsistencies. Each managed object is assigned a unique ID when the object is saved (a temporary ID is assigned if the object has never been saved), and an application can use this ID to ensure data consistency when using multiple contexts. However, the application is then forced to track object changes, which doesn't make sense since that's the job of Core Data. In short, it's best to use multiple contexts if there is a compelling reason.
Figure 5 illustrates the concept of how the Managed Object Context manages the core data objects (NSManagedObject
).
Figure 5.
One of the downsides of the Managed Object Context is that all of the objects are operated on together: It is not possible to save just one NSManagedObject
instance because all are saved at once. If your application must work with a lot of NSManagedObjects
at the same time, be careful about the amount of memory used. In the sample application, when creating 250,000 Car
records, 260 MB is used. To put this memory usage in context, the iPhone 5s has 1 GB of memory and the iPhone 4s has 512 MB of memory. On an iPhone 5s, if more than 350,000 records are created, the test application receives a memory warning (-(void)didReceiveMemoryWarning
is called). Yes, 260 MB is a lot, but remember that the each of the 250,000 objects are in memory, fully realized meaning all the Car
member variables are also in memory. If you restart the sample application and fetch the 250,000 records you just created, you will notice the amount of memory used is 105 MB much less than the 260 MB used to create the records what happened? The answer comes courtesy of one of Core Data's powerful features: object faulting.
Object faulting is the ability of the Managed Object Context to load an object in memory when it is accessed. If the object is never accessed, the memory is never allocated, thus reducing memory usage. This happens behind the scenes: Your application runs along happily. In the attached sample application, when accessing the CarType
from the Car
object, if the CarType
isn't in memory; Core Data will automatically load it in memory with the correct data for you. The NSManagedObject
method isFault
enables your application to determine if the object is truly in memory or loaded into memory when accessed.
Creating a managed object is a little different. Instead of the usual alloc
init
pattern to create a new object instance, an application should call the NSEntityDescription insertNewObjectForEntityForName
method using the Managed Object Context as an input argument. The following code snippet shows how the sample code Car
object is created:
Car *car = [NSEntityDescription insertNewObjectForEntityForName:@"Car" inManagedObjectContext:_managedObjectContext];
NSEntityDescription insertNewObjectForEntitryForName
is actually a convenience method. Underneath the hood, an NSManagedObject
is created for the entity @Car
and inserted into the NSManagedObjectContext
. The important point here is to understand that the Managed Object Context knows about all of the objects.