Atomic Store Life-cycle

This article describes the life-cycle of an atomic store. You should read this article to learn what methods are invoked on a store when, and what the store is expected to do as a result. This will help you to understand how to implement a custom store.

Registration

To use a custom store type in an application, you must register the store type with the NSPersistentStoreCoordinator class using registerStoreClass:forStoreType:. For more details, see Registering a Custom Store Type.

Initialization and Loading Data

You create an instance of store of a given type using the NSPersistentStoreCoordinator method addPersistentStoreWithType:configuration:URL:options:error:. You must implement the appropriate methods to properly initialize your store and load its contents.

Initializing the Store

The coordinator allocates a new store and initializes it using with initWithPersistentStoreCoordinator:configurationName:URL:options:. This method is invoked both for new stores and for existing stores; your implementation of initWithPersistentStoreCoordinator:configurationName:URL:options: should therefore check whether a file exists at the given URL—if it does not, you must create it.

You must also initialize the metadata for the store. After initWithPersistentStoreCoordinator:configurationName:URL:options:, the coordinator invokes metadata on the new store; at this point the metadata must be correct.

After the store is initialized, coordinator instructs the store to load its data by sending it a load: message.

Loading a File

The load: method should retrieve the store’s data from the URL specified for the store in initWithPersistentStoreCoordinator:configurationName:URL:options:. The load: method should parse the contents of the store to extract the persistent data and create a reference data object—such as a dictionary—for each element.

For each object to be represented, your store must do the following:

  1. Create a managed object ID with the reference data and entity type for the node, using objectIDForEntity:referenceObject:.

    The object ID for a cache node is also the object ID for a managed object representing the cache node.

  2. Create a cache node (an instance of NSAtomicStoreCacheNode), using initWithObjectID:.

    The store must provide a mutable property cache object for each node.

  3. (Optional) Push the corresponding persisted data into the node.

    This step may be omitted if you implement lazy loading or other behavior.

    The default implementation of NSAtomicStoreCacheNode provides key-value coding compliant access for property storage, so for the common case you do not need to create a custom subclass of NSAtomicStoreCacheNode.

Once all of the nodes have been created, you register them with the store using addCacheNodes:.

At this point, the cache now has registered nodes and the framework is ready. Core Data will handle all requests for fetches, and create managed objects from the underlying cache node information.

This process is described in greater detail, with examples, in Initialization and Loading Data.

Zero-Length Files

Any subclass of NSAtomicStore must be able to handle being initialized with a URL pointing to a zero-length file. This serves as an indicator that a new store is to be constructed at the specified location and allows you to securely create reservation files in known locations which can then be passed to Core Data to construct stores.

You may choose to create zero-length reservation files during an initWithPersistentStoreCoordinator:configurationName:URL:options: or load: method. If you do so, you must remove the reservation file if the store is removed from the coordinator before it is saved.

Processing and Saving Changes

When a managed object context is sent a save: message, any associated store must (in this order) process all of the inserted, updated, and deleted objects that are assigned to it (the store), and then commit any changes to the external repository.

  1. New objects

    For each newly-inserted object in the context, the store receives a newReferenceObjectForManagedObject: message. Your store must provide a unique—and unchanging—value for this instance and entity type for the store (see New Reference Objects).

    After the reference data is returned, the store requests a new cache node be created for each of these objects using newCacheNodeForManagedObject:.

    NSAtomicStore provides a default implementation that should suffice in most cases, However, if you wish to customize the creation of the cache nodes or use custom cache nodes, you can override newCacheNodeForManagedObject:. In your implementation, you should create the cache node with the managed object ID from included managed object, and the values to be persisted should be pushed from the managed object into the cache node. You then register the new nodes with the store, using addCacheNodes:

  2. Updated objects

    For all objects which are changed in the context, the store receives a updateCacheNode:fromManagedObject: message. The arguments for the method are the cache node and managed object pair (which share the same object ID.) The store must push the persisted values from the managed object into the cache node.

  3. Deleted objects

    Your store receives a willRemoveCacheNodes: callback; if necessary you can override this method to break any strong reference cycles to ensure that deleted objects are reclaimed.

  4. Saving changes

    Finally, the store receives a save: message to commit the changes to the external repository.

    Your store must write the data (the cache nodes) and the metadata to the URL specified for the store in whatever format it uses.

New Reference Objects

Your implementation of newReferenceObjectForManagedObject: must return a stable (unchanging) value for any given object. If you don’t do this, then Save As and migration operations will not work correctly.

This means that you can only use arbitrary numbers, UUIDs, or other random values if you save the value together with the rest of the data for a given object. If you cannot save the originally-assigned reference object, then newReferenceObjectForManagedObject: must derive the reference object from the managed object's values. As this constraint exists for Save As and store migration operations, you could also use a side table or in-memory structure to ensure that a newly-migrated store loads its atomic store cache nodes with the same objectIDs that the current managed object context is using for the migrated managed objects.

Removal and Deallocation

If a store is to be removed from the coordinator, it receives the message willRemoveFromPersistentStoreCoordinator:. In a custom atomic store class, you can override this method to implement clean-up or other behavior. If you do, your implementation of willRemoveFromPersistentStoreCoordinator: should invoke super’s implementation.

Note that the coordinator parameter may be nil if:

In these cases, willRemoveFromPersistentStoreCoordinator: is invoked to allow you to perform any necessary resource cleanup (such as removal of reservation files—see Zero-Length Files).