Persistent Store Features

Core Data provides several types of persistent store. This article describes the features and benefits of each, and how you can migrate from one type of store to another.

Store Types and Behaviors

Core Data provides three sorts of disk-based persistent store—XML, atomic, and SQLite—and an in-memory store. (Core Data provides the binary store type—NSBinaryStoreType—as a built-in atomic store; you can also create your own atomic store types—see “Custom store types.”) From the application code perspective, in general you should not be concerned about implementation details for any particular store. You should interact with managed objects and the persistence stack. There are, however, some behavioral differences between the types of store that you should consider when deciding what type of store to use.

XML

Atomic

SQLite

In-Memory

Speed

Slow

Fast

Fast

Fast

Object Graph

Whole

Whole

Partial

Whole

Other Factors

Externally parseable

No backing required

Store-specific behavior

Given the abstraction that Core Data offers, there is typically no need to use the same store throughout the development process. It is common, for example, to use the XML store early in a project life-cycle, since it is fairly human-readable and you can inspect a file to determine whether or not it contains the data you expect. In a deployed application that uses a large data set, you typically use an SQLite store, since this offers high performance and does not require that the entire object graph reside in memory. You might use the binary store if you want store writes to be atomic. There are, however, some features and considerations that are specific to particular store types. These are described in following sections.

Custom store types

In OS X v10.5 and later you can create your own atomic store types. For details, see Atomic Store Programming Topics.

In OS X v10.4 , you cannot write your own object store which interoperates transparently with the Core Data stack. You can, however, manage object persistence yourself by using an in-memory store. Before you load your data, you create an in-memory store. When you load your data, you create instances of the appropriate model classes and insert them into a managed object context, associate them with the in-memory store (see insertObject: and assignObject:toPersistentStore:). The managed objects are then fully integrated into the Core Data stack and benefit from features such as undo management. You are also responsible, however, for saving the data. You must register to receive NSManagedObjectContextDidSaveNotification notifications from the managed object context, and upon receipt of the notification save the managed objects to the persistent store.

Security

Core Data makes no guarantees regarding the security of persistent stores from untrusted sources and cannot detect whether files have been maliciously modified. The SQLite store offers slightly better security than the XML and binary stores, but it should not be considered inherently secure. Note that you should also consider the security of store metadata since it is possible for data archived in the metadata to be tampered with independently of the store data. If you want to ensure data security, you should use a technology such as an encrypted disk image.

Fetch Predicates and Sort Descriptors

There are some interactions between fetching and the type of store. In the XML, binary, and in-memory stores, evaluation of the predicate and sort descriptors is performed in Objective-C with access to all Cocoa's functionality, including the comparison methods on NSString. The SQL store, on the other hand, compiles the predicate and sort descriptors to SQL and evaluates the result in the database itself. This is done primarily for performance, but it means that evaluation happens in a non-Cocoa environment, and so sort descriptors (or predicates) that rely on Cocoa cannot work. The supported sort selectors are compare: and caseInsensitiveCompare:, localizedCompare:, localizedCaseInsensitiveCompare:, and localizedStandardCompare: (the latter is Finder-like sorting, and what most people should use most of the time). In addition you cannot sort on transient properties using the SQLite store.

There are additional constraints on the predicates you can use with the SQLite store:

- CoreData supports a noindex: (see NSPredicate documentation re: function expressions) that can be used to drop indices in queries passed to SQLite. This is done primarily for performance reasons: SQLite uses a limited number of indices per query, and noindex: allows the user to preferentially specify which indexes should not be used.

SQLite Store

File-systems supported by the SQLite store

The SQLite store supports reading data from a file that resides on any type of file-system. The SQLite store does not in general, however, support writing directly to file-systems which do not implement byte-range locking. For DOS filesystems and for some NFS file system implementations that do not support byte-range locking correctly, SQLite will use "<dbfile>.lock" locking, and for SMB file systems it uses flock-style locking.

To summarize: byte-range locking file systems have the best concurrent read/write support; these include HFS+, AFP, and NFS. File systems with simplistic file locking are also supported but do not allow for as much concurrent read/write access by multiple processes; these include SMB, and DOS. The SQLite store does not support writing to WebDAV file-systems (this includes iDisk).

File Size May Not Reduce After Deleting a Record

Simply deleting a record from a SQLite store does not necessarily result in a reduction in the size of the file. If enough items are removed to free up a page in the database file, SQLite’s automatic database vacuuming will reduce the size of the file as it rearranges the data to remove that page. Similarly, the file size may be reduced if you remove an item that itself occupies multiple pages (such as a thumbnail image).

An SQLite file is arranged as a collection of pages. The data within those pages is managed via B-trees, not as simple fixed-length records. This is much more efficient for searching and for overall storage, since it allows SQLite to optimize how it stores both data and indexes in a single file, and is also the foundation of its data integrity (transaction and journaling) mechanism. However, the cost of this is that some delete operations may leave holes in the file. If you delete some data and add other data, the holes left by the deleted data may be filled by the added data, or the file may be vacuumed to compact its data, whichever SQLite considers most appropriate based on the operations you’re performing.

Configuring a SQLite Store’s Save Behavior

When Core Data saves a SQLite store, SQLite updates just part of the store file. Loss of that partial update would be catastrophic, so you may want to ensure that the file is written correctly before your application continues. Unfortunately, doing so means that in some situations saving even a small set of changes to an SQLite store can take considerably longer than saving to, say, an XML store. (For example, where saving to an XML file might take less than a hundredth of a second, saving to an SQLite store may take almost half a second. This is not an issue for XML or Binary stores—since they are atomic, there is a much lower likelihood of data loss that involves corruption of the file, especially since the writes are typically atomic and the old file is not deleted until the new has been successfully written.)

Core Data provides a way to control sync behavior in SQLite using two independent pragmas, giving you control over the tradeoff between performance and reliability:

  • synchronous controls the frequency of disk-syncing

    PRAGMA synchronous FULL [2] / NORMAL [1] / OFF [0]

  • full_fsync controls the type of disk-sync operation performed

    PRAGMA fullfsync 1 / 0

    In OS X v10.5, the default is 0.

The pragmas are publicly documented at http://sqlite.org/pragma.html.

You can set both pragmas using the key NSSQLitePragmasOption in the options dictionary when opening the store. The NSSQLitePragmasOption dictionary contains pragma names as keys and string values as objects, as illustrated in the following example:

NSPersistentStoreCoordinator *psc = <#Get a persistent store coordinator#>;
 
NSMutableDictionary *pragmaOptions = [NSMutableDictionary dictionary];
[pragmaOptions setObject:@"NORMAL" forKey:@"synchronous"];
[pragmaOptions setObject:@"1" forKey:@"fullfsync"];
 
NSDictionary *storeOptions =
    [NSDictionary dictionaryWithObject:pragmaOptions forKey:NSSQLitePragmasOption];
NSPersistentStore *store;
NSError *error = nil;
store = [psc addPersistentStoreWithType:NSSQLiteStoreType
            configuration: nil
            URL:url
            options:storeOptions
            error:&error];