Using a Managed Object Model

This article describes how you use a managed object model in your application.

Creating and Loading a Managed Object Model

You usually create a model in Xcode, as described in Core Data Model Editor Help. You can also create a model entirely in code, as show in Listing 3 and described in Core Data Utility Tutorial—typically, however, this is too long-winded to consider in anything but the most trivial application. (You are nevertheless encouraged to review the tutorial to gain an understanding of what the modeling tool does, and in particular to gain an appreciation that the model is simply a collection of objects.)

Compiling a Data Model

A data model is a deployment resource. In addition to details of the entities and properties in the model, a model you create in Xcode contains information about the diagram—its layout, colors of elements, and so on. This latter information is not needed at runtime. The model file is compiled using the model compiler, momc, to remove the extraneous information and make runtime loading of the resource as efficient as possible. An xcdatamodeld “source” directory is compiled into a momd deployment directory, and an xcdatamodel “source” file is compiled into a mom deployment file.

momc is located in /Developer/usr/bin/. If you want to use it in your own build scripts, its usage is momc source destination, where source is the path of the Core Data model to compile and destination is the path of the output.

Loading a Data Model

In some cases, you do not have to write any code to load a model. If you use a document-based application on OS X, NSPersistentDocument manages the task of finding and loading your application’s model for you. If you use Xcode to create a non-document application that uses Core Data (for OS X or for iOS), the application delegate includes code to retrieve the model. The name of a model—as represented by the filename used to store it on disk—is not relevant at runtime. Once the model is loaded by Core Data, the filename is meaningless and has no use, so you can name the model file whatever you like.

If you want to load a model yourself, there are two mechanisms you can use:

  • You can load a single model from a specific URL, using the instance method initWithContentsOfURL:.

    This is the generally-preferred technique. Typically an application has a single model, and using this method you ensure that you load only that model. You can also load individual models via URLs and then unify them using modelByMergingModels: before instantiating a coordinator with them.

    In cases where you have more than one model—and particularly in cases where the models represent different versions of the same schema—knowing which model to load is essential (merging together models with the same entities at runtime into a single collection would cause naming collisions and errors). This method is also useful if you want to store the model outside of the bundle for your application, and so need to reference it via a file-system URL.

  • You can create a merged model from a specific collection of bundles, using the class method mergedModelFromBundles:.

    This method may be useful in cases where segregation of models is not important—for example, you may know your application and a framework it links to both have models you need or want to load. The class method allows you to easily load all of the models at once without having to consider what the names are, or put in specialized initialization code to ensure all of your models are found.

Problems May Arise if Your Project Contains More Than One Model

There are a few situations in which you may encounter problems when trying to load a model. Typically these are caused by the build products of your project being out of date combined with use of the class method mergedModelFromBundles:.

  • If you simply rename your model file, Core Data attempts to merge the current and the old versions and you get an error similar to the following:

    reason = "'Can't merge models with two different entities named 'EntityName''";
  • If you create a new model that contains different entities from those in your original model, then Core Data merges the old and new models. If you have an existing store, you get an error similar to the following when you attempt to open it:

    reason = "The model used to open the store is incompatible with the one used to create the store";

There are two solutions:

  • Make sure that you clean any old build products before running the application. If the application bundle itself contains old model files, you can delete the application.

  • Instead of mergedModelFromBundles:, use initWithContentsOfURL: to initialize the model. The URL uniquely identifies a model so that Core Data will not merge the current model with any legacy models.

Changing the Schema Makes a Model Incompatible With Old Stores

Because a model describes the structure of the data in a persistent store, changing any parts of a model that alters the schema renders it incompatible with (and so unable to open) the stores it previously created. If you change your schema, you therefore need to migrate the data in existing stores to new version (see Core Data Model Versioning and Data Migration Programming Guide). For example, if you add a new entity or a new attribute to an existing entity, you will not be able to open old stores; if you add a validation constraint or set a new default value for an attribute, you will be able to open old stores.

Accessing and Using a Managed Object Model at Runtime

You sometimes need to gain access to the model at runtime, typically to—for example—retrieve a fetch request template, a localized entity name, or perhaps the data type of an attribute. You may also want to programmatically modify the model (you can do this only before it is used at runtime, see NSManagedObjectModel). There are a number of ways you can access a managed object model at runtime. Through the persistence stack you ultimately get the model from the persistent store coordinator. Thus to get the model from a managed object context, you use the following code:

[[<#A managed object context#> persistentStoreCoordinator] managedObjectModel];

You can also retrieve the model from an entity description, so given a managed object you can retrieve its entity description and hence the model, as shown in the following example.

[[<#A managed object#> entity] managedObjectModel];

In some cases, you maintain a “direct” reference to the model—that is, a method that returns the model directly. NSPersistentDocument provides managedObjectModel that returns the model associated with the persistent store coordinator used by the document's managed object context. If you use the Core Data Application template, the application delegate maintains a reference to the model.

Creating Fetch Request Templates Programmatically

You can create fetch request templates programmatically and associate them with a model using setFetchRequestTemplate:forName: as illustrated in Listing 1. Recall, though, that you can only modify the model before it has been used by a store coordinator.

Listing 1  Creating a fetch request template programmatically

NSManagedObjectModel *model = <#Get a model#>;
NSFetchRequest *requestTemplate = [[NSFetchRequest alloc] init];
NSEntityDescription *publicationEntity =
    [[model entitiesByName] objectForKey:@"Publication"];
[requestTemplate setEntity:publicationEntity];
 
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
    @"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
        (mainAuthor.lastName like[cd] $LAST_NAME) AND \
        (publicationDate > $DATE)"];
[requestTemplate setPredicate:predicateTemplate];
 
[model setFetchRequestTemplate:requestTemplate
    forName:@"PublicationsForAuthorSinceDate"];

Accessing Fetch Request Templates

You can retrieve and use a fetch request template as illustrated in the code fragment in “Accessing and Using a Managed Object Model at Runtime.” The substitution dictionary must contain keys for all the variables defined in the template; if you want to test for a null value, you must use an NSNull object—see “Using Predicates”.

Listing 2  Using a fetch request template

NSManagedObjectModel *model = <#Get a model#>;
NSError *error = nil;
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
    @"Fiona", @"FIRST_NAME", @"Verde", @"LAST_NAME",
    [NSDate dateWithTimeIntervalSinceNow:-31356000], @"DATE", nil];
NSFetchRequest *fetchRequest =
    [model fetchRequestFromTemplateWithName:@"PublicationsForAuthorSinceDate"
            substitutionVariables:substitutionDictionary];
NSArray *results =
    [aManagedObjectContext executeFetchRequest:fetchRequest error:&error];

If the template does not have substitution variables, you must either:

  1. Use fetchRequestFromTemplateWithName:substitutionVariables: and pass nil as the variables argument; or

  2. Use fetchRequestTemplateForName: and copy the result.

    If you try to use the fetch request returned by fetchRequestTemplateForName:, this generates an exception ("Can't modify a named fetch request in an immutable model").

Localizing a Managed Object Model

You can localize most aspects of a managed object model, including entity and property names and error messages. It is important to consider that localization also includes "localization into your own language." Even if you do not plan to provide foreign-language versions of your application, you can provide a better experience for your users if error messages show "natural language" names rather than "computer language" names (for example, "First Name is a required property" rather than "firstName is a required property").

You localize a model by providing a localization dictionary that follows the pattern shown in the table below.

Table 1  Keys and values in a localization dictionary for a managed object model

Key

Value

Note

"Entity/NonLocalizedEntityName"

"LocalizedEntityName"

"Property/NonLocalizedPropertyName/Entity/EntityName"

"LocalizedPropertyName"

1

"Property/NonLocalizedPropertyName"

"LocalizedPropertyName"

"ErrorString/NonLocalizedErrorString"

"LocalizedErrorString"

Note: (1) For properties in different entities with the same non-localized name but which should have different localized names.

You can access the localization dictionary using the method localizationDictionary. Note, however, that in the implementation in OS X version 10.4, localizationDictionary may return nil until Core Data lazily loads the dictionary for its own purposes (for example, reporting a localized error).

Strings File

The easiest way to localize a model is to create a corresponding strings file—the strings file name is the same as the model file name, but with a .strings rather than a .xcdatamodel extension (for example, for a model file named MyDocument.xcdatamodel the corresponding strings file is MyDocumentModel.strings—if your model file name already includes the suffix "Model", you must append a further "Model", so the strings file corresponding to JimsModel.xcdatamodel would be the rather unlikely-looking JimsModelModel.strings). The file format is similar to a standard strings file you use for localization (see “Localizing String Resources”) but the key and value pattern follows that shown in Table 1.

A strings file for a model that includes an employee entity might contain the following:

"Entity/Emp" = "Employee";
"Property/firstName" = "First Name";
"Property/lastName" = "Last Name";
"Property/salary" = "Salary";

Setting a Localization Dictionary Programmatically

You can set a localization dictionary at runtime using the NSManagedObjectModel method setLocalizationDictionary:. You must create a dictionary with keys and values as shown in Table 1, and associate it with the model. You must ensure you do this before the model is used to fetch or create managed objects, because the model is read-only after doing so. The listing shown in Listing 3 illustrates the creation in code of a managed object model including a localization dictionary. The entity is named "Run" and is represented at runtime by the Run class. The entity has two attributes, "date" and "processID"—a date and an integer respectively. The process ID has a constraint that its value must not be less than zero.

Listing 3  Creating a managed object model in code

NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] init];
NSEntityDescription *runEntity = [[NSEntityDescription alloc] init];
[runEntity setName:@"Run"];
[runEntity setManagedObjectClassName:@"Run"];
[mom setEntities:@[runEntity]];
 
NSMutableArray *runProperties = [NSMutableArray array];
 
NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];
[runProperties addObject:dateAttribute];
[dateAttribute setName:@"date"];
[dateAttribute setAttributeType:NSDateAttributeType];
[dateAttribute setOptional:NO];
 
NSAttributeDescription *idAttribute= [[NSAttributeDescription alloc] init];
[runProperties addObject:idAttribute];
[idAttribute setName:@"processID"];
[idAttribute setAttributeType:NSInteger32AttributeType];
[idAttribute setOptional:NO];
[idAttribute setDefaultValue:@0];
 
NSPredicate *validationPredicate = [NSPredicate predicateWithFormat:@"SELF >= 0"];
NSString *validationWarning = @"Process ID < 0";
[idAttribute setValidationPredicates:@[validationPredicate]
    withValidationWarnings:@[validationWarning]];
 
[runEntity setProperties:runProperties];
 
NSDictionary *localizationDictionary = @{
    @"Property/processID/Entity/Run" : @"Process ID",
    @"Property/date/Entity/Run" : @"Date"
    @"ErrorString/Process ID < 0" : @"Process ID must not be less than 0" };
[mom setLocalizationDictionary:localizationDictionary];